1、线程和线程池Thread&ThreadPool

    //线程初始化时执行方法可以带一个object参数,为了传入自定义参数,所以执行需单独调用用于传参。
Console.WriteLine("执行线程");
Thread th = new Thread((objParam) =>
{
Console.WriteLine("线程启动,执行匿名方法,有无参数{0}", objParam != null);
});
th.IsBackground = true;
object objP = new object();
th.Start(objP); //线程池
//线程池初始化执行方法必须带一个object参数,接受到的值是系统默认NULL(不明),所以初始化完成自动调用
Console.WriteLine("执行线程池");
ThreadPool.QueueUserWorkItem((objparam) =>
{
Console.WriteLine("线程池加入的匿名方法被执行。");
});

执行结果:

2、并行循环Parallel

    int result = 0;
int lockResult = 0;
object lb = new object();
//并行循环
//并行应该用于一次执行多个相同任务,或计算结果和循环的游标没有关系只和执行次数有关系的计算
Console.WriteLine("执行并行循环");
Parallel.For(0, 10, (i) =>
{
result = result + 2;
//lock只能lock引用类型,利用引用对象的地址唯一作为锁,实现lock中的代码一次只能一个线程访问
//lock让lock里的代码在并行时变为串行,尽量不要在parallel中用lock(lock内的操作耗时小,lock外操作耗时大时,并行还是起作用)
lock(lb)
{
lockResult = lockResult + 2;
Thread.Sleep(100);
Console.WriteLine("i={0},lockResult={1}", i, lockResult);
}
Console.WriteLine("i={0},result={1}", i, result);
});

执行结果:

Parallel用法很简单,就是Parallel.For(游标开始值, 游标结束, int参数的Action),传入action的方法接受的int参数就是当前执行的游标。

跑题开始-------------------------------(手贱要在并行里写lock还要sleep刚好形成规律,以下是写博时发现的,没兴趣的同学可以跳过)

通过结果我们可以看出,首先执行顺序是随机的,可以猜到一次是把游标的取值分别当参数传给多个线程执行action即可。后面的结果也验证了这一点,lockResult不用说,不管多少线程到这都得排队执行,所以结果递增。再看result,上来就变成了10,可以推出遇到lock之前已经被加了5次,那么应该是一次4个线程喽(大家肯定觉得应该是5个,开始我也是这样觉得,往下看)。

再看result其实也不是没规律,可以看出从10到20也是递增,但到了20就不增加了(因缺思亭)。我们模拟下(按5个线程模拟不符合结果,我就直接按合理的情况推一遍)。

1、首先可以4个线程ABCD同时执行,都到了lock这停住,那这时result被加了4次是8。

2、然后一个线程A执行lock里的代码,其他的BCD等待(不是sleep仍然占用cpu),执行完输出lockResult=2(第一行)。

这时继续往下应该输出result=8对吧,为什么是result=10。注意lock里有一个Thread.Sleep(100),这就是关键。在lock里sleep会怎样,当前线程A释放cpu 100ms,这时就可以再来一个线程E执行到lock这也停住了,result是不是就是10了。

3、这个线程A醒来优先级最高挤掉一个线程往下继续输出result=10(第二行)。这时刚才被挤掉的线程又恢复占用cpu状态,就是BCDE四个线程。

4、同理,BCDE四个等待线程的又有一个进入lock然后又sleep,又可以有一个线程来把result加2,这时循环这个过程,result也呈现出规律。

5、为什么result后面几次都是20,因为总共执行10次,首先四个线程执行了4次,然后一个新线程执行第5次后,第1次执行的线程才输出第5次执行后的结果,第2次输出第6次。。。第6次输出第10次(第6行就是result=20),后面四次已经执行过result加2,所以只输出结果20。

如果把Thread.Sleep(100)去掉result就不再有这么明显的规律。因为sleep让cpu可以释放与lock等待共同作用让线程执行形成一个先后顺序的队列。sleep放到lock外也不行,sleep会释放cpu,放到lock外,没有lock占用cpu,lock前就不一定执行了几次。

为什么一次是四个线程呢,很容易想到,我CPU四核的。。。就这么简单。。

跑题结束---------------------------------

  

通过以上分析,并行是个什么东西大家应该有所了解了,继续。

3、任务Task

    //任务
Task.Run(() =>
{
Thread.Sleep(200);
Console.WriteLine("Task启动执行匿名方法");
});
Console.WriteLine("Task默认不阻塞"); //获取Task.Result会造成阻塞等待task执行
int r = Task.Run(() =>
{
Console.WriteLine("Task启动执行匿名方法并返回值");
Thread.Sleep(1000);
return 5;
}).Result;
Console.WriteLine("返回值是{0}", r);

执行结果:

用法如上,好像使用的是线程池。传入方法不能有参数,可以有返回值。要获得结果,要在Run()(返回Task类型)之后调用Task类型的Result属性获取。可以看出,获取结果时,Task是会阻塞当前进程的,等待线程执行完毕才继续。

Task好用,关键点就是有返回值,可以获取结果。

4、异步方法Async&await&Task

重点:

1、异步方法需要Async关键字修饰

2、异步方法的返回类型只能是void或Task

3、返回值类型是T时,异步方法返回类型必须是Task

4、await可以用于async方法和 async方法中的task(通过3、4两点大家应该能猜到,异步方法本身其实就是一个Task或者说和自己内部的Task在同一线程)

5、只有异步方法内使用了(await关键词描述的)(有返回值的线程Task)才能提现异步方法的优势写了一个异步方法,一个普通方法进行对比测试。异步方法正确使用的代码如下:(后面几次测试在此基础上稍作修改即可)

    //异步方法
public async Task<int> MethodA(DateTime bgtime, int i)
{
int r = await Task.Run(() =>
{
Console.WriteLine("异步方法{0}Task被执行", i);
Thread.Sleep(100);
return i * 2;
});
Console.WriteLine("异步方法{0}执行完毕,结果{1}", i, r); if (i == 49)
{
Console.WriteLine("用时{0}", (DateTime.Now - bgtime).TotalMilliseconds);
}
return r;
}
//普通方法
public int MethodC(DateTime bgtime, int i)
{
int r = Task.Run(() =>
{
Console.WriteLine("普通多线程方法{0}Task被执行", i);
Thread.Sleep(100);
return i * 2;
}).Result;
Console.WriteLine("普通方法{0}执行完毕,结果{1}", i, r); if (i == 49)
{
Console.WriteLine("用时{0}", (DateTime.Now - bgtime).TotalMilliseconds);
}
return r;
}
    //异步方法
public async Task<int> MethodA(DateTime bgtime, int i)
{
int r = await Task.Run(() =>
{
Console.WriteLine("异步方法{0}Task被执行", i);
Thread.Sleep(100);
return i * 2;
});
Console.WriteLine("异步方法{0}执行完毕,结果{1}", i, r); if (i == 49)
{
Console.WriteLine("用时{0}", (DateTime.Now - bgtime).TotalMilliseconds);
}
return r;
}
//普通方法
public int MethodC(DateTime bgtime, int i)
{
int r = Task.Run(() =>
{
Console.WriteLine("普通多线程方法{0}Task被执行", i);
Thread.Sleep(100);
return i * 2;
}).Result;
Console.WriteLine("普通方法{0}执行完毕,结果{1}", i, r); if (i == 49)
{
Console.WriteLine("用时{0}", (DateTime.Now - bgtime).TotalMilliseconds);
}
return r;
}

测试开始!------------------------------------------------------------------------------------------------

  • 第一次:都获取Task的返回结果,异步方法使用await获取,普通方法使用Task.Run().Result获取。

测试结果:

    可以发现普通方法由于阻塞执行都是按顺序执行,多线程失去意义。异步方法则并行执行,重要的是计算结果一样。所以在方法内需要使用Task结果时,异步方法使用await不阻塞调用进程优势明显。

  • 第二次:异步方法中不使用await,使用和普通方法一样的Task.Run().Result获取结果。测试结果:

    可以看到用时和执行顺序都一样。所以没有await的情况下,异步方法等待Task结果时一样会阻塞调用进程。

  • 第三次:都只调用Task执行,不获取结果。测试结果:

    可以看到,不管是普通方法还是异步方法都是多个线程并行执行,所以不获取结果的时,异步方法和普通多线程方法性能一样。

    在这次测试基础上,让异步方法await一个不返回结果的Task会发现,异步方法内还是会等待Task执行完毕。所以只要使用await不管是方法还是Task,有无返回结果,后面的代码都要等待其执行完毕。

如下面示例:

    public async void AsyncMethod()
{
Console.WriteLine("开始异步代码"); Thread.Sleep(5000); Console.WriteLine("开始执行异步方法"); //只要有await关键字,当前线程默认都会阻塞当前线程,但不阻塞主线程,如想不阻塞当前线程,当前线程不应包含await执行
int R = await Task.Run(() =>
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine("异步执行" + i.ToString() + "..");
Thread.Sleep(2000); ; //模拟耗时操作
}
return 1;
}); //直接Task.Run有返回值时会阻塞当前线程,同时也会阻塞主线程
//int R = Task.Run(() =>
//{
// for (int i = 0; i < 5; i++)
// {
// Console.WriteLine("异步执行" + i.ToString() + "..");
// Thread.Sleep(2000); ; //模拟耗时操作
// }
// return 1;
//}).Result; //不会阻塞当前线程,也不会阻塞主线程
//Task.Run(() =>
//{
// for (int i = 0; i < 5; i++)
// {
// Console.WriteLine("异步执行" + i.ToString() + "..");
// Thread.Sleep(2000); ; //模拟耗时操作
// }
//}); Console.WriteLine("异步代码执行完毕");
}
  • 第四次:把ACTesct方法改成Async异步方法,再用await调用asy.MethodA()异步方法。测试结果:

    await只能在异步方法中使用(为什么这样设计后面分析),所以ACTest需要改成Async。可以看到,异步方法调用时被await了一样会等待。所以异步方法应该没有返回值或者调用时不关注返回结果才有效。

测试完毕!-----------------------------------------------------------------------------------

5、总结:

  【意义】异步方法的意义就是保证一个进程使用多线程多次执行一个方法时,不会因为其中某一次执行阻塞调用进程
  
  【原理】利用方法内Task调用新线程,await使方法内等待Task结果时调用进程不被阻塞,多次调用相当于多个线程并行。(不被阻塞的原因应该是异步方法本身就和内部的Task跑在一个线程里)
  
  【区别】普通方法只用Task也可以并行,当方法内需要Task返回值时,等待Task结果就会阻塞调用进程
  
  【应用】主要应用在没有返回值,使用线程且需要线程返回结果的方法

一些分析:

  • 1.异步方法有返回值会怎样?

    因为异步方法返回类型是Task,所以获取返回值只能await或者.Result,两

者都会让当前方法等待。

  • 2.那么异步方法是不是没有作用了?

    如果是用.Result获取,那么是。如果是await就不一定了。await只能在async方法中使用,所以await获取异步方法返回值的方法也是异步的,再往上最终只能肯定是一个普通方法调用异步方法。是否有用取决于普通方法内调用最上层异步方法的方式。

  • 3.为什么返回值类型是T,方法返回类型需要是Task?

    要达到异步方法内等待线程结果不阻塞调用进程,这个方法本身就应该在线程中执行。所以不管返回类型是什么,放到Task中运行后返回的是Task。这样被调用时相当于一个Task.Run(),也就可以实现异步方法await了。

  • 4.为什么要实现异步方法await可等待?

    异步方法的await其实第二点已经分析了,实现异步方法await可以允许异步方法内继续调用异步方法,把异步操作从底层向上层传递。而能够传递到的最上层是什么,是static void Main(),所以最终还是普通方法调用异步方法。也就是说不能继续使用await等待异步方法的结果了,当最上层不关注返回结果时,不管内部有多少次await异步方法的调用,依然还是多线程的并行。如果最上层非要关注异步方法的返回结果,用.Result获取其结果,那我无话可说。

  • 5.关于Async和await。

    await其实不光是一个简单的让下一行代码等待异步方法或Task结果的关键字。应该理解成一个扩大当前Task代码执行范围的命令。

    从最开始的await Task让整个异步方法B都能在Task中运行(所以普通方法调用异步方法B时,B内await Task结果就不会阻塞调用进程)。

    到异步方法A中await异步方法B让异步方法A和B都在同一Task内运行(所以普通方法调用异步方法A时,A内await异步方法B的结果和B内await Task的结果就不会阻塞调用进程)

    Async用于标识一个方法是异步方法,约束其返回类型为Task。也就说内部可以使用await,且方法本身是放到Task中执行的,所以代码返回类型T,方法的返回类型却是Task。

    最后一定要区别异步方法和普通多线程方法的用处,他们的关键区别就是是否需要单独等待线程的执行结果。不要把异步方法当多线程方法用了。

原文地址:https://www.cnblogs.com/xianyudotnet/p/5716908.html

Thread&ThreadPool、Parallel、Async和Await用法总结的更多相关文章

  1. .NET多线程(Thread,ThreadPool,Task,Async与Await)

    .NET多线程是什么? 进程与线程 进程是一种正在执行的程序. 线程是程序中的一个执行流. 多线程是指一个程序中可以同时运行多个不同的线程来执行不同的任务. .NET中的线程 Thread是创建和控制 ...

  2. async和await用法

    原文:async和await用法 要理解async和await的用法,首先要了解Task相关知识,这里不做说明,因为这不是本文的重点. 如果你已经对Task很了解,那么如何使用async和await, ...

  3. async和await用法(Task)

    原文:async和await用法 要理解async和await的用法,首先要了解Task相关知识,这里不做说明,因为这不是本文的重点. 如果你已经对Task很了解,那么如何使用async和await, ...

  4. 浅谈async函数await用法

    今天状态不太好,睡久了懵一天. 以前只是了解过async函数,并还没有很熟练的运用过,所以先开个坑吧,以后再结合实际来更新下,可能说的有些问题希望大家指出. async和await相信大家应该不陌生, ...

  5. C#中async和await用法

    .net 4.5中新增了async和await这一对用于异步编程的关键字. async放在方法中存在await代码的方法中,await放在调用返回Task的方法前. class Class1 { pr ...

  6. 关于Thread ThreadPool Parallel 的一些小测试demo

    using System; using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading ...

  7. C# Async与Await用法

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  8. c# async和await 用法(阻塞与不阻塞)

    void PagePaint() { Console.WriteLine("Paint Start"); Paint(); Console.WriteLine("Pain ...

  9. 从Thread,ThreadPool,Task, 到async await 的基本使用方法解读

    记得很久以前的一个面试场景: 面试官:说说你对JavaScript闭包的理解吧? 我:嗯,平时都是前端工程师在写JS,我们一般只管写后端代码. 面试官:你是后端程序员啊,好吧,那问问你多线程编程的问题 ...

随机推荐

  1. Python urlib 模块

    Python urlib 模块 urlib 模块 当前再爬虫领域使用的比较少,不过它对图片爬取处理会比较方便.这里我们只使用它的图片爬取. 使用 urlib.request.urlretrieve(u ...

  2. 【机器学习】PCA

    目录 PCA 1. PCA最大可分性的思想 2. 基变换(线性变换) 3. 方差 4. 协方差 5. 协方差矩阵 6. 协方差矩阵对角化 7. PCA算法流程 8. PCA算法总结 PCA PCA 就 ...

  3. 基本认证(Basic Authorization)

    ---------------------------------- import arcpy from base64 import encodestring username = 'xxx' pas ...

  4. ADO.NET中的5个主要对象

    1.Connection:主要是开启程序和数据库之间的连接.没有利用连接对象将数据库打开,是无法从数据库中取得数据的. Close和Dispose的区别,Close以后还可以Open,Dispose以 ...

  5. docfx 简单使用方法、自动生成目录的工具

    [摘要] 这是我编写的一个 Docfx 文档自动生成工具,只要写好 Markdown 文档,使用此工具可为目录.文件快速生成配置,然后直接使用 docfx 运行即可. https://github.c ...

  6. 写入文件writelines 换行问题

    知识点:在python中没有数组的概念,有列表.元组.字典的概念 问题描述: 在写循环语句的时候,我需要把输出的列表存放到文件上,但是如果没有换行的话,存下的文件就是一坨的字. 所以在存入文件的时候就 ...

  7. python中property简单使用与实现

    property简单使用 class P: """ property简单使用 """ def __init__(self,name): se ...

  8. Confluence 邮箱设置

    Confluence有两种方法设置邮箱 原理: confluence服务器配置好邮箱信息,用户触发邮件发送规则时,confluence服务使用已配置的邮箱信息登录到邮箱服务器,进行发件服务. 那么我们 ...

  9. 201871010123-吴丽丽《面向对象程序设计(Java)》第十一周学习总结

    201871010123-吴丽丽<面向对象程序设计(Java)>第十一周学习总结 项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ ...

  10. 201871010106-丁宣元 《面向对象程序设计(java)》第八周学习总结

    201871010106-丁宣元 <面向对象程序设计(java)>第八周学习总结 正文开头: 项目 内容 这个作业属于哪个课程 https://home.cnblogs.com/u/nwn ...