线程

sikky615 2017-04-01 03:56:55
一. FrameWork 4.0之前的线程世界    

   在.NET FrameWork 4.0之前,如果我们使用线程。一般有以下几种方式:

使用System.Threading.Thread 类,调用实例方法Start()开启一个新线程,调用Abort()方法来提前终止线程。
使用System.Threading.ThreadPool类,调用静态方法QueueUserWorkItem(),将方法放入线程池队列,线程池来控制调用。
使用BeginInvoke,EndInvoke,BeginRead,EnRead,BeginWrite,EndWrite等一系列的异步方法。
使用System.ComponentModel.BackgroundWorker控件,调用实例方法RunWorkerAsync(),开启一个新线程。 
二. .Net 传统异步编程概述

异步编程模型 (APM),在该模型中异步操作由一对 Begin/End 方法(如 FileStream.BeginRead 和 Stream.EndRead)表示。
基于事件的异步模式 (EAP),在该模式中异步操作由名为“操作名称Async”和“操作名称Completed”的方法/事件对(例如 WebClient.DownloadStringAsync 和 WebClient.DownloadStringCompleted)表示。 (EAP 是在 .NET Framework 2.0 版中引入的,在silverlight或者wpf变成中经常用到)。
三. Task 的优点以及功能

在任务启动后,可以随时以任务延续的形式注册回调。
通过使用 ContinueWhenAll 和 ContinueWhenAny 方法或者 WaitAll 方法或 WaitAny 方法,协调多个为了响应 Begin_ 方法而执行的操作。
在同一 Task 对象中封装异步 I/O 绑定和计算绑定操作。
监视 Task 对象的状态。
使用 TaskCompletionSource 将操作的状态封送到 Task 对象。


众所周知,async方法只可以返回void,Task和Task<T>。



对于返回void的async方法,它并不是awaitable,所以其他方法不能用await方法来调用它,而返回Task的async方法则可以。



那么当async方法返回Task后,接着await,那被await的Task是一个什么概念?是async方法中第一个被await的Task?不,它代表目标async方法的全部执行,其中包括被await分割的连接Task,但是不包括非await造成的多线程执行。



如下代码,在doo是一个返回Task的async方法,然后在另一个方法test中await调用doo,然后在Main方法中调用test(由于Main方法不允许加async,所以需要另外加一个async方法来使用await)

static void Main(string[] args)

{

test();

log("Main:调用test后");

Thread.Sleep(Timeout.Infinite);

}



//Main方法不允许加async,所以我们用这个方法使用await

static async void test()

{

log("test: await之前");

await doo();

log("test: await之后");

}



//返回Task的async方法

static async Task doo()

{

log("doo: Task结果:" + await Task.Run(() => { Thread.Sleep(1000); log("Task"); return 1; }));

log("doo: Task结果:" + await Task.Run(() => { Thread.Sleep(1000); log("Task"); return 2; }));

log("doo: Task结果:" + await Task.Run(() => { Thread.Sleep(1000); log("Task"); return 3; }));

Thread.Sleep(1000);

Console.WriteLine("doo中在Task外的Thread.Sleep执行完毕");

}



//输出方法:显示当前线程的ManagedThreadId

static void log(string msg)

{

Console.WriteLine("{0}: {1}", Thread.CurrentThread.ManagedThreadId, msg);

}



上面代码会输出:

1: test: await之前

1: Main:调用test后

3: Task

3: doo: Task结果:1

4: Task

4: doo: Task结果:2

3: Task

3: doo: Task结果:3

doo中在Task外的Thread.Sleep执行完毕

3: test: await之后



前两句简单,调用test方法,await后的内容会被加在目标Task的后面,然后test马上返回,于是输出“Main:调用test后”,同时他们都是在主线程中执行的,所以ManagedThreadId都是1。



接着后面就是另一个Task的执行(当然在另一个线程,也是test方法中await的目标Task)。这个所谓的Task就是doo方法的全部执行。所以doo中三个顺序执行的Task(通过await一个一个连接)依次执行,所以Task输出结果1,2,3。第一个Task的ManagedThreadId是3,第二个是4,第三个又是3,原因是Task的内部执行使用了CLR的线程池,所以线程得到了重复利用。



接着doo方法还没有完,最后一个await造成doo方法后面的代码在这个await针对的Task执行后继续执行,于是输出:doo中Task外的Thread.Sleep执行完毕。



最后当doo彻底执行完test的await才结束,所以最后一行输出:test:await之后。





上面我说过:被await的async方法返回的Task代表“目标async方法的全部执行,其中包括被await分割的连接Task,但是不包括非await造成的多线程执行”。

所以如果把返回Task的async方法(也就是上例中的doo方法)改成这样:

//返回Task的async方法

static async Task doo()

{

log("doo: Task结果:" + await Task.Run(() => { Thread.Sleep(1000); log("Task"); return 1; }));

log("doo: Task结果:" + await Task.Run(() => { Thread.Sleep(1000); log("Task"); return 2; }));

log("doo: Task结果:" + await Task.Run(() => { Thread.Sleep(1000); log("Task"); return 3; }));



//不使用await:线程池多线程

ThreadPool.QueueUserWorkItem(_ =>

{

Thread.Sleep(1000);

Console.WriteLine("ThreadPool.QueueUserWorkItem");

});



//不使用await:Task多线程

Task.Run(() =>

{

Thread.Sleep(1000);

Console.WriteLine("Task.Run");

});

}



我们加入了不用await的多线程执行,分别使用ThreadPool和Task,整个程序会输出这样的结果:

1: test: await之前

1: Main:调用test后

3: Task

3: doo: Task结果:1

4: Task

4: doo: Task结果:2

3: Task

3: doo: Task结果:3

3: test: await之后

Task.Run

ThreadPool.QueueUserWorkItem



不使用await的多线程完全脱离了test方法中await的Task,是运行在test的await之后的。



另外Visual Studio会对Task.Run代码做如下警告:





提示:Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the ‘await’ operator to the result of the call.

就是说,如果不加await,当前方法会继续执行直到结束,不用管他,因为我们现在就是在做在async方法中不用await的测试,呵呵。





或许你会问,为什么要用这样的方式去await另一个async方法返回的Task呢?我们一直在讨论返回Task的async方法,我认为看一个返回Task<T>的async方法可以更好地解释这个问题。



下面我们把上面的代码改成相似的返回Task<int>的async方法执行,那么doo方法返回Task<T>,他把自己方法内3个awaited Task的结果统一相加,最后返回结果并作为自己返回的Task的结果。然后在test方法中输出doo返回的结果。



完整代码:

static void Main(string[] args)

{

test();

log("Main:调用test后");

Thread.Sleep(Timeout.Infinite);

}



//Main方法不允许加async,所以我们用这个方法使用await

static async void test()

{

log("test: await之前");

Console.WriteLine("doo结果:{0}", await doo());

log("test: await之后");

}



//返回Task的async方法

static async Task<int> doo()

{

var res1 = await Task.Run(() => { Thread.Sleep(1000); log("awaited Task1执行"); return

var res2 = await Task.Run(() => { Thread.Sleep(1000); log("awaited Task2执行"); return

var res3 = await Task.Run(() => { Thread.Sleep(1000); log("awaited Task3执行"); return



//不使用await:线程池多线程

ThreadPool.QueueUserWorkItem(_ =>

{

Thread.Sleep(1000);

Console.WriteLine("ThreadPool.QueueUserWorkItem");

});



//不使用await:Task多线程

Task.Run(() =>

{

Thread.Sleep(1000);

Console.WriteLine("Task.Run");

});



return res1 + res2 + res3;

}



//输出方法:显示当前线程的ManagedThreadId

static void log(string msg)

{

Console.WriteLine("{0}: {1}", Thread.CurrentThread.ManagedThreadId, msg);

}



先看结果:

1: test: await之前

1: Main:调用test后

3: awaited Task1执行

4: awaited Task2执行

4: awaited Task3执行

doo结果:6

4: test: await之后

ThreadPool.QueueUserWorkItem

Task.Run





和上一个返回Task的例子一样,当在test方法中await doo方法返回的Task,doo内awaited Task都被先等了,而没有awaited的线程都并没有被等,这是为什么呢(也就是上面留下的那个问题)?下面用这个返回Task<int>的例子解释一下:

在test中await doo返回的Task,那么此时我们需要他的结果,而他的结果是需要自己方法内所包含的其他awaited结果,可以理解成被等的子结果。所以自己的结果需要其他的结果,那么等这个结果必须需要等那些被依赖的结果也出来。所以test方法await doo方法的结果会同样等待所有doo内的await,不会管其他doo内非await的多线程执行(当然从技术角度讲,也是不可能的,因为async/await可以这样全靠的是编译器)。

...全文
404 6 打赏 收藏 转发到动态 举报
写回复
用AI写文章
6 条回复
切换为时间正序
请发表友善的回复…
发表回复
「已注销」 2017-04-07
  • 打赏
  • 举报
回复
mark...
jingbo11 2017-04-05
  • 打赏
  • 举报
回复
我是来学习的。。。
sp1234_maJia 2017-04-01
  • 打赏
  • 举报
回复
嗯,如果把
Task.WaitAll(task1, task2, task3);
改为
await Task.WhenAll(task1, task2, task3);
则是一个很好的解释 wait 的例子,这就是把阻塞式的程序改为了异步回调式的程序,可以说明 await 的内涵。
  • 打赏
  • 举报
回复
如过不把异步回调机制很容易地讲清楚,那么 await 反而越讲越迷糊了,反而乱用。
  • 打赏
  • 举报
回复
接近你的例子的写法(最少改动的写法)可以这样:
//返回Task的async方法

static async Task<int> doo()

{
    var task1 = Task.Run(() =>
    {
        Thread.Sleep(1000); log("awaited Task1执行"); return 1;
    });
    var task2 = Task.Run(() =>
    {
        Thread.Sleep(100); log("awaited Task2执行"); return 2;
    });
    var task3 = Task.Run(() =>
    {
        Thread.Sleep(10); log("awaited Task3执行"); return 3;
    });

    Task.WaitAll(task1, task2, task3);

    ThreadPool.QueueUserWorkItem(_ =>
    {
        Thread.Sleep(1000);
        Console.WriteLine("ThreadPool.QueueUserWorkItem");
    });


    //不使用await:Task多线程
    Task.Run(() =>
{
    Thread.Sleep(1000);
    Console.WriteLine("Task.Run");
});
    return task1.Result + task2.Result + task3.Result;

}
使用 WaitAll,真正并发处理计算任务,而不是用异步并发语法实际上写出一个顺序的过程。 其实一眼就能看出一堆 await 代码是用并发异步语法来写了一个顺序的过程,因为 await 就是异步回调的意思,就好像是每一个学过 jQuery 等等脚本的人都大量地用到回调函数一样,嵌套3个 await 就是嵌套3个回调函数,自然就是顺序执行的,而并不是并发执行的。
  • 打赏
  • 举报
回复
不要写
    var res1 = await Task.Run(() => { Thread.Sleep(1000); log("awaited Task1执行"); return

    var res2 = await Task.Run(() => { Thread.Sleep(1000); log("awaited Task2执行"); return

    var res3 = await Task.Run(() => { Thread.Sleep(1000); log("awaited Task3执行"); return
这类的代码。假设你把故意延时设置为1秒、0.1秒、10毫秒,会发现,仍然是顺序执行的。 一个貌似高大上的多线程异步语法代码,结果总是顺序执行的,这个“线程、异步”其实就是多此一举啦。

17,740

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 .NET Framework
社区管理员
  • .NET Framework社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧