Thread,ThreadPool,Task, 到async await 的基本使用方法和理解 - 常山造纸农 - 博客园
Excerpt
很久以前的一个面试场景: 面试官:说说你对JavaScript闭包的理解吧? 我:嗯,平时都是前端工程师在写JS,我们一般只管写后端代码。 面试官:你是后端程序员啊,好吧,那问问你多线程编程的问题吧。 我:一般没用到多线程。 面试官:……………………….. (面试
很久以前的一个面试场景:
面试官:说说你对JavaScript闭包的理解吧?
我:嗯,平时都是前端工程师在写JS,我们一般只管写后端代码。
面试官:你是后端程序员啊,好吧,那问问你多线程编程的问题吧。
我:一般没用到多线程。
面试官:……………………….. (面试结束)
好了,哈哈一笑后,我们来看看 Thread,ThreadPool,Task, async, await 的基本使用方法。
1.Thread
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <span>private</span> <span>static</span> <span>void</span> Main(<span>string</span><span>[] args) { System.Console.WriteLine(</span><span>"</span><span>主线程开始</span><span>"</span><span>); </span><span>var</span> thread = <span>new</span> Thread(<span>new</span><span> ThreadStart(ThreadTest)); thread.Start();
System.Console.WriteLine(</span><span>"</span><span>主线程结束</span><span>"</span><span>); System.Console.ReadLine(); }
</span><span>private</span> <span>static</span> <span>void</span><span> ThreadTest() { System.Console.WriteLine(</span><span>"</span><span>开始执行子线程.... </span><span>"</span><span>); Thread.Sleep(</span><span>100</span><span>); }</span>
|
执行结果:
上面的代码,大家应该都很好理解,通过new Thread 来创建一个子线程,然后.Start() 开始执行。
我们F12 ThreadStart 看到 public delegate void ThreadStart(); 是一个无参数无返回值的委托,那么,如果要在线程中执行一个有参数的方法怎么办了?
OK,我们看Thread的构造函数
ParameterizedThreadStart 是什么?按字面上意思就是带参数的ThreadStart,继续F12看看它
果然是可以带一个object的参数。
改造一下刚才的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <span>private</span> <span>static</span> <span>void</span> Main(<span>string</span><span>[] args) { System.Console.WriteLine(</span><span>"</span><span>主线程开始</span><span>"</span><span>); </span><span>var</span> thread = <span>new</span> Thread(<span>new</span><span> ParameterizedThreadStart(ThreadTest)); thread.Start(</span><span>10</span><span>);
System.Console.WriteLine(</span><span>"</span><span>主线程结束</span><span>"</span><span>); System.Console.ReadLine(); }
</span><span>private</span> <span>static</span> <span>void</span> ThreadTest(<span>object</span><span> p) { System.Console.WriteLine(</span><span>"</span><span>开始执行子线程.... 参数:{0} </span><span>"</span><span>, p); Thread.Sleep(</span><span>100</span><span>); }</span>
|
执行结果:
(当然还可以用ThreadStart(()=>{ }) 直接用lambda表达式的方式,这里就不写示例代码了 )
看到上面的执行结果,子线程因为Thread.Sleep(100) ,所以每次都最后才打印出输出结果,那么你可能会疑问,如果我想等子线程执行完,我再执行主线程后面的代码,怎么办?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <span>private</span> <span>static</span> <span>void</span> Main(<span>string</span><span>[] args) { System.Console.WriteLine(</span><span>"</span><span>主线程开始</span><span>"</span><span>); </span><span>var</span> thread = <span>new</span> Thread(<span>new</span><span> ParameterizedThreadStart(ThreadTest)); thread.Start(</span><span>10</span><span>); thread.Join();
System.Console.WriteLine(</span><span>"</span><span>主线程结束</span><span>"</span><span>); System.Console.ReadLine(); }<br> </span><span>private</span> <span>static</span> <span>void</span> ThreadTest(<span>object</span><span> p) { System.Console.WriteLine(</span><span>"</span><span>开始执行子线程.... 参数:{0} </span><span>"</span><span>, p); Thread.Sleep(</span><span>100</span><span>); }</span>
|
注意看, 加了这句 thread.Join() ,管他什么意思,我们先看看执行结果吧!
OK,是不是明白Join()的意义了?
2.ThreadPool
为什么有了Thread还要出现ThreadPool了?
如果你的代码设计了大量使用Thread,那么有可能会超过系统最大的线程数导致崩溃,而且每次创建和销毁线程也是很耗资源,ThreadPool就可以帮你提高代码效率并管理你的线程。
这不是重点,今天重点是学习它的基础使用方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <span>private</span> <span>static</span> <span>void</span> Main(<span>string</span><span>[] args) { System.Console.WriteLine(</span><span>"</span><span>主线程开始</span><span>"</span><span>); ThreadPool.QueueUserWorkItem(</span><span>new</span> WaitCallback(ThreadTest), <span>1</span><span>);
System.Console.WriteLine(</span><span>"</span><span>主线程结束</span><span>"</span><span>); System.Console.ReadLine(); }
</span><span>private</span> <span>static</span> <span>void</span> ThreadTest(<span>object</span><span> p) { System.Console.WriteLine(</span><span>"</span><span>开始执行子线程.... 参数:{0} </span><span>"</span><span>, p); Thread.Sleep(</span><span>100</span><span>); }</span>
|
先看看WaitCallback的定义
一个带参数的委托,这就要求它的委托方法必须带一个object的参数了。
ThreadPool静态类通过QueueUserWorkItem()方法将工作函数排入线程池,它不需要我们主动的.Start(),那么他能不能Join()了?
我们点一下就知道了,它既不要你手动Start也没有Join这样的方法。
好了,简单学习Thread和ThreadPool后,发现他们构造函数中都是没有返回值的委托,如果我们要在主线程中获取子线程执行方法的返回值,怎么办? Task闪亮登场了!
3.Task
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <span>private</span> <span>static</span> <span>void</span> Main(<span>string</span><span>[] args) { System.Console.WriteLine(</span><span>"</span><span>主线程开始</span><span>"</span><span>); </span><span>//</span><span> new Task 创建方式-不带参数 </span><span>//</span><span>Task task = new Task(ThreadTest); </span><span>//</span><span>task.Start();
</span><span>//</span><span> new Task 创建方式-带参数 </span><span>//</span><span>Task task=new Task(() => ThreadTest(10));
</span><span>//</span><span>Task.Factory 创建方式-不带参数 </span><span>//</span><span>Task task = Task.Factory.StartNew(ThreadTest); </span><span>//</span><span>task.Start();
</span><span>//</span><span>Task.Factory 创建方式-带参数 </span><span>//</span><span>Task task = Task.Factory.StartNew(() => ThreadTest(10)); </span><span>//</span><span>task.Start();</span> <span> Task task </span>= Task.Run(() =><span> ThreadTest()); </span><span>//</span><span>Task task = Task.Run(() => ThreadTest(10));</span> <span> System.Console.WriteLine(</span><span>"</span><span>主线程结束</span><span>"</span><span>); System.Console.ReadLine();<br> }</span>
|
Task 的三种创建线程的方法,Task.Run() 不需要手动Start() 。其他两种方式是需要手动Start()。 他们没有Join()方法,取而代之的是Wait()
我们用Run()方法为例,看Task如何获取方法的返回值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <span>private</span> <span>static</span> <span>void</span> Main(<span>string</span><span>[] args) { System.Console.WriteLine(</span><span>"</span><span>主线程开始</span><span>"</span><span>);
Task</span><<span>int</span>> task = Task.Run(() => ThreadTest(<span>10</span><span>)); </span><span>var</span> result =<span> task.Result;
System.Console.WriteLine(</span><span>"</span><span>主线程结束,result={0}</span><span>"</span><span>, result); System.Console.ReadLine(); }
</span><span>private</span> <span>static</span> <span>int</span> ThreadTest(<span>int</span><span> i) { Thread.Sleep(</span><span>100</span><span>); System.Console.WriteLine(</span><span>"</span><span>子线程开始</span><span>"</span><span>); </span><span>return</span> i * <span>100</span><span>; }</span>
|
执行结果:
通过task.Result 我们获取到了在子线程中ThreadTest方法的返回值。有没有注意,主线程是等子线程执行完之后才打印最后输出的! task.Result 除了拿到返回值外,是不是和Wait()类似?
看到这里,你肯定会想到,这样另起线程去跑耗时作业和我们平时普通写法有什么区别?效率上会高很多吗?我们来测试看看!
常规方法:
1 2 3 4 5 6 7 8 9 10 11 12
| <span>private</span> <span>static</span> <span>void</span> Main(<span>string</span><span>[] args) { DateTime dt1 </span>=<span> DateTime.Now; </span><span>int</span> count = <span>0</span><span>; </span><span>for</span> (<span>int</span> i = <span>0</span>; i < <span>10</span>; i++<span>) { Thread.Sleep(</span><span>10</span><span>); count </span>+=<span> i; } System.Console.WriteLine(</span><span>"</span><span>执行完成,耗时=</span><span>"</span> + (DateTime.Now -<span> dt1).TotalMilliseconds); System.Console.ReadLine(); }</span>
|
Task方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <span>private</span> <span>static</span> <span>void</span> Main(<span>string</span><span>[] args) { DateTime dt1 </span>=<span> DateTime.Now; Task</span><<span>int</span>> task = Task.Run(() =><span> { </span><span>int</span> count = <span>0</span><span>; </span><span>for</span> (<span>int</span> i = <span>0</span>; i < <span>10</span>; i++<span>) { Thread.Sleep(</span><span>10</span><span>); count </span>+=<span> i; } </span><span>return</span><span> count; });
</span><span>var</span> result =<span> task.Result;
System.Console.WriteLine(</span><span>"</span><span>执行完成,耗时=</span><span>"</span> + (DateTime.Now -<span> dt1).TotalMilliseconds); System.Console.ReadLine(); }</span>
|
这就很尴尬了,用Task反而执行时间更长!!! 是不是我的打开方式不对?
4.async await
async是修饰一个异步方法的关键字。有两种返回类型(void 或者 Task)
await必须是在async修饰的异步方法体内,await后面必须是一个异步方法或者Task。表示异步等待后面方法的结果。
1.返回void的使用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <span>private</span> <span>static</span> <span>void</span> Main(<span>string</span><span>[] args) { System.Console.WriteLine(</span><span>"</span><span>主线程开始</span><span>"</span><span>); </span><span>for</span> (<span>int</span> i = <span>0</span>; i < <span>10</span>; i++<span>) { ThreadTest(i); } System.Console.WriteLine(</span><span>"</span><span>主线程执行完成</span><span>"</span><span>); System.Console.ReadLine(); }
</span><span>private</span> <span>static</span> <span>async</span> <span>void</span> ThreadTest(<span>int</span><span> i) { </span><span>await</span> Task.Run(() =><span> { Thread.Sleep(</span><span>10</span><span>); System.Console.WriteLine(</span><span>"</span><span>子线程开始,i=</span><span>"</span> +<span> i); }); }</span>
|
执行结果
2.返回Task的使用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <span>private</span> <span>static</span> <span>void</span> Main(<span>string</span><span>[] args) { System.Console.WriteLine(</span><span>"</span><span>主线程开始</span><span>"</span><span>); </span><span>var</span> task =<span> ThreadTest(); System.Console.WriteLine(</span><span>"</span><span>主线程执行完成,result=</span><span>"</span>+<span> task.Result); System.Console.ReadLine(); } </span><span>private</span> <span>static</span> <span>async</span> Task<<span>int</span>><span> ThreadTest() { </span><span>var</span> count = <span>0</span><span>; </span><span>await</span> Task.Run(() =><span> { </span><span>for</span> (<span>int</span> i = <span>0</span>; i < <span>10</span>; i++<span>) { Thread.Sleep(</span><span>10</span><span>); count </span>+=<span> i; System.Console.WriteLine(</span><span>"</span><span>count=</span><span>"</span>+<span> count); } }); </span><span>return</span><span> count; }</span>
|
返回的是Task,那么像得到它的返回值,肯定也是通过.Result了,我们肯定有疑问了,这样和直接写Task有什么区别? 只是为了更加方便和美观吗?
接下来我们来测试下执行效率!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <span>private</span> <span>static</span> <span>void</span> Main(<span>string</span><span>[] args) { DateTime dt1 </span>=<span> DateTime.Now; </span><span>var</span> t =<span> ThreadTest().Result; System.Console.WriteLine(</span><span>"</span><span>执行完成,耗时=</span><span>"</span> + (DateTime.Now - dt1).TotalMilliseconds + <span>"</span><span> count=</span><span>"</span> +<span> t); System.Console.ReadLine(); }
</span><span>private</span> <span>static</span> <span>async</span> Task<<span>int</span>><span> ThreadTest() { </span><span>var</span> count = <span>0</span><span>; </span><span>await</span> Task.Run(() =><span> { </span><span>for</span> (<span>int</span> i = <span>0</span>; i < <span>10</span>; i++<span>) { Thread.Sleep(</span><span>10</span><span>); count </span>+=<span> i; } }); </span><span>return</span><span> count; }</span>
|
执行效率和之前没什么区别,不知道这种测试方式是否合理?跪求大神们分享赐教!
今天就写到这里,关于 async await 和Task区别,async await 线程阻塞问题,后面再来仔细研究。
========================================================================================================
分割线
========================================================================================================
昨天这篇博客发布后,收到大神的批评和指教,非常感谢!
读了这篇文章后,才恍然大悟。 文章链接:https://msdn.microsoft.com/zh-cn/magazine/jj991977.aspx
分析下昨天测试“性能”的实例代码的使用错误:
1.盲目使用task.Result来获取最终结果,这样导致主线程阻塞,都是等待子线程执行完毕。这样的时间差比没有太多意义。
2.都是在一个Task.Run()中模拟一个耗时操作,内部循环Thread.Sleep(10)。这样把耗时操作搬到一个子线程去做,就算快也能快到哪里去了,完全没有体现出多线程的优越性。
下面是修改后的测试代码,再看看async await给程序带来的性能:
1.普通Task,通过task.Result获取返回值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <span>private</span> <span>static</span> <span>void</span> Main(<span>string</span><span>[] args) { DateTime dt1 </span>=<span> DateTime.Now;
</span><span>for</span> (<span>int</span> i = <span>0</span>; i <= <span>50</span>; i++<span>) { </span><span>var</span> re = Task.Run(() =><span> { Thread.Sleep(</span><span>10</span><span>); </span><span>return</span><span> i; }).Result;
System.Console.WriteLine(</span><span>"</span><span>result=</span><span>"</span> +<span> re);
</span><span>if</span> (i == <span>50</span><span>) System.Console.WriteLine(</span><span>"</span><span>执行完成,耗时=</span><span>"</span> + (DateTime.Now -<span> dt1).TotalMilliseconds); } System.Console.ReadLine(); }</span>
|
2.使用async await
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <span>private</span> <span>static</span> <span>void</span> Main(<span>string</span><span>[] args) { DateTime dt1 </span>=<span> DateTime.Now;
</span><span>for</span> (<span>int</span> i = <span>0</span>; i <= <span>50</span>; i++<span>) { </span><span>var</span> task =<span> ThreadTest(dt1, i); }
System.Console.ReadLine(); }
</span><span>private</span> <span>static</span> <span>async</span> Task<<span>int</span>> ThreadTest(DateTime dt1, <span>int</span><span> i) { </span><span>int</span> re = <span>await</span> Task.Run(() =><span> { Thread.Sleep(</span><span>10</span><span>); </span><span>return</span><span> i; });
System.Console.WriteLine(</span><span>"</span><span>result=</span><span>"</span> +<span> re);
</span><span>if</span> (i == <span>50</span><span>) System.Console.WriteLine(</span><span>"</span><span>执行完成,耗时=</span><span>"</span> + (DateTime.Now -<span> dt1).TotalMilliseconds);
</span><span>return</span><span> re; }</span>
|
async await 真正体现了它的性能所在 。
总结:
1.不要盲目使用task.Result
2.async await的意义(或者说和Task的区别)在于不阻塞线程的情况下获取返回值。
本文博客园地址:http://www.cnblogs.com/struggle999/p/6933376.html