.net 项目中不可避免地要与线程打交道,目的都是实现异步、并发。从最开始的new Thread()入门,到后来的Task.Run(),如今在使用async/await的时候却有很多疑问。
先来看一段代码:使用Task实现异步
Task.Run(() => { message = (IBytesMessage)consumer.Receive(m_Interval); });
Receive()方法是一个延迟返回的方法,m_Interval是超时时间。如果采用同步方式执行Receive()的话,那整个程序就会被这个方法堵塞。我个人最习惯的处理方式就用Task.Run()。可惜项目要求必须使用.net framework3.5,所以只能退而求其次,放弃Task,使用Thread或者ThreadPool。
使用Thread实现异步:
new Thread(() => { message = (IBytesMessage)consumer.Receive(m_Interval); }).Start();
直接new Thread().start()这个写法是很危险的,这里只做参考。在C# 5以后的书籍中,你可能会看到这样一句话:一旦你输入了new Thread(),那就糟糕了,说明项目的代码太过时了。
使用ThreadPool实现异步:
ThreadPool.QueueUserWorkItem(Listen);
private void Listen(object state) { message = (IBytesMessage)consumer.Receive(m_Interval); }
ThreadPool 内部有一套完整的线程管理机制,可以让开发者完全忽略Thread的生命周期控制。但ThreadPool中的线程,都是后台线程,当主线程执行完毕时,程序并不会等待后台线程的执行,而是直接退出。Thread则是前台线程,主程序会等待所有前台线程执行完毕后才会退出。另外在使用ThreadPool的时候需要注意QueueUserWorkItem的参数类型是:
public delegate void WaitCallback(object state) 所以,Listen方法有一个未用到的参数state。
综上,Task还是最优的解决方案。
说到这,问题看似解决了,.net 4.0及以上 Task是不二之选,低版本择优先选择ThreadPool,特殊情况考虑Thread。那么 .net4.5的新特性 async/await 有什么用呢?上述情况需要用到async/await 吗?
这里我们需要看一下完整的代码
private void Listen(object state) { message = (IBytesMessage)consumer.Receive(m_Interval); if (message != null) { m_IAsyncMesssgae.OutputMessage(message.ToString()); } else { m_IAsyncMesssgae.OutputException(new Exception("Wait timed out.")); } } public void OnStartAsync() { try { if (m_IsConnected && !m_IsListening) { connectionWPM?.Start(); m_IsListening = true; ThreadPool.QueueUserWorkItem(Listen); } } catch (Exception ex) { m_IAsyncMesssgae.OutputException(ex); } finally { OnStop(); } }
这里红色字体的m_IAsyncMesssgae是一个回调的接口实例,也就说,此代码中,通过接口回调的方式把Receive()方法延迟返回的message返回给调用者。目前的代码是可以满足需求的。
我们试着用asynv和await实现一下这个需求。
public async void OnStartAsync() { if (m_IsConnected && !m_IsListening) { connectionWPM?.Start(); m_IsListening = true; message = await Task.Run(()=> {return (IBytesMessage)consumer.Receive(m_Interval); }); } }
1)async/await 和刚才说的Thread Task ThreadPool并不是一个概念。前者是控制异步和并发的关键字,后者是对线程的三种实现方式。
2)async/await只能和Task结合使用,async标记的方法 只能有三种返回值Task,Task<T>,void(不建议,因为async/await 就是为了获取异步方法的返回值)。
3)await等待的内容也必须是Task或者Task<T> 上面代码隐藏了一个内容,其实Task.Run()也是一个返回值为Task<T>的方法。
4)await还有一个作用是将Task<T>转成T。
5)在同一个用async标记的方法内,所有在await代码段之后的代码 都要等待await后的内容执行完成后才能执行。
6)如果一个非async方法 调用async方法获取异步返回值,那么就无法成功获取异步返回值。
再把返回值void修改一下:
public async Task<IBytesMessage> OnStartAsync() { if (m_IsConnected && !m_IsListening) { connectionWPM?.Start(); m_IsListening = true; message = await Task.Run(()=> {return (IBytesMessage)consumer.Receive(m_Interval); }); } return message; }
这样一来,外部调用时候,就不需要接口回调了,直接调用OnStartAsync就可以了。切记!调用OnStartAsync的方法必须也是async,否则就直接返回message的默认值,而不是等待TaskRun()的执行。await只在所属的async方法内奏效。
调用OnStartAsync也有种不同的写法:
//写法1 async Task Handle() { string re = await OnStartAsync(); //dosth } //写法2 async Task Handle() { var re = OnStartAsync(); //dosth do(await re); }
//写法3
void Handle() { var re = OnStartAsync(); do(re); }
写法1:dosth需要等待 OnStartAsync执行完毕后再执行。
写法2:dosth先执行,然后再执行do(await re)
写法3:根本就无法获取正确的返回值,实则没有等待异步执行,而是直接返回了。
以上,水平有限,如有不足,敬请指正。如有侵权 请联系作者删除。