Task-杂记
c# 异步编程变得越来越简单,这归功与async 和 await 。
异步编程相比大家随便找篇文章都能了解到他的含义和用法了,今天主要想弄清楚异步的执行顺序这些。先上一段代码
static void Main(string[] args) { Console.WriteLine("主线程ID:", Thread.CurrentThread.ManagedThreadId); var name = SayHello(); Console.WriteLine("执行结束"); Console.Read(); } static async Task<string> SayHello() { Console.WriteLine("Task.Run之前,线程Id: {0}", Thread.CurrentThread.ManagedThreadId); return await Task.Run(() => { Thread.Sleep(1000); Console.WriteLine("'Task执行中,线程Id: {0}", Thread.CurrentThread.ManagedThreadId); return "Hello World"; }); }
执行结果:
很清楚的看到,再执行Task.Run之前,还是主线程ID的,然后执行之后能,就会开启另外的线程继续执行。
改变一下代码:
static void Main(string[] args) { Console.WriteLine($"主线程ID:{Thread.CurrentThread.ManagedThreadId}"); var name = SayHello2(); Console.WriteLine(name.Result); Console.WriteLine("执行结束"); Console.Read(); } static Task<string> SayHello2() { Console.WriteLine("Task.Run之前,线程Id: {0}", Thread.CurrentThread.ManagedThreadId); return Task.Run(() => { Thread.Sleep(1000); Console.WriteLine("'Task执行中,线程Id: {0}", Thread.CurrentThread.ManagedThreadId); return "Hello World"; }); }
执行结果为:
当我们试图要访问Task.Result的时候,实际上就会等待Task执行完成。
下面探讨一下:ConfigureAwait。官方解释为:尝试将延续任务封送回原始上下文,则为 true
;否则为 false
。
是的,当我们用await 关键字将 一个方法的同步代码分割开后,前后两段的代码的线程Id会一样吗? 带着这个思考我又改了一下程序:
static async Task<string> SayHello() { Console.WriteLine("同步代码块1线程Id: {0}", Thread.CurrentThread.ManagedThreadId); var name = await Task.Run(() => { //Thread.Sleep(1000); Console.WriteLine("'Task执行中,线程Id: {0}", Thread.CurrentThread.ManagedThreadId); return "Hello World"; }); Console.WriteLine("同步代码块2线程Id: {0}", Thread.CurrentThread.ManagedThreadId); var name2 = await Task.Run(() => { //Thread.Sleep(1000); Console.WriteLine("'Task执行中,线程Id: {0}", Thread.CurrentThread.ManagedThreadId); return "Hello World"; }); Console.WriteLine("同步代码块3线程Id: {0}", Thread.CurrentThread.ManagedThreadId); return name; }
执行结果有点出乎我的意料,在分格的代码块执行使用了新的线程,而没有回到调用它的主线程执行,尽管我并没有使用ConfigureAwait(false)
看了下资料,发现这个ConfigureaAwait的概念在UI和Asp.net 的上下文才关键。我们看一下一个WindowForm代码
public static async Task WaitAsync() { // 这里 awati 会捕获当前上下文…… await Task.Delay(TimeSpan.FromSeconds(1));// ……这里会试图用上面捕获的上下文继续执行 } public static void Deadlock() { // 开始延迟 Task task = WaitAsync(); // 同步程序块,正在等待异步方法完成 task.Wait(); }
这样的代码执行会产生死锁,因为WaitAsyn里面先阻塞了UI线程,然后再执行完await task之后先要调回主线程执行,但是已经阻塞了,所以就造成了死锁。我们只需要用ConfigureaAwait改一下
public static async Task WaitAsync() { await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); } public static void Deadlock() { Task task = WaitAsync(); // 同步程序块,正在等待异步方法完成 task.Wait(); }
这样死锁就可以避免了。但是这里引申出一个原则:你代码使用了异步,最好一直使用异步,应该在调用结束时用wait等待他的执行结果,避免出现task.Wait.Task<T>.Result这样的代码。然后就是
在默认情况下,一个 async 方法在被 await 调用后恢复运行时,会在原来的上下文中运行。
如果是 UI 上下文,并且有大量的 async 方法在 UI 上下文或者Asp.Net 上下文中恢复,就会引起性能上的问题。