早上无意中看到了async和await关键字,花了十几分钟看了一下msdn,大概明白了是什么一个东西,和大家分享一下。
await关键字的中文是期待的意思。在我们编程中想表达“我待会期待这里会有一个值,但我不是现在就要,我先去做其他事情,你完成的时候告诉我”。其实异步模式非常符合现实中场景,现实生活中还真的很少东西是同步的。等车的时候没事干可以拿手机出来玩一下,首发下邮件,而不是直愣愣的干在那里等着车过来。
话说回来,在C# 5中借助await可以更好的辅助我们进行异步编程模式的开发,通过改变执行流程,使得异步执行看起来更像同步执行。
一个async方法里通常包含一个或多个的对应的await操作符,但如果没有 await表达式也不会导致编译错误。但如果调用一个async方 法,却不使用await关键字来标记一个挂起点的话,程序将会忽略async关键字并以同步的方式执行。编译器会对类似的问题发出警告。
我直接拿msdn里的代码和图来举例吧。
public partial class MainWindow : Window { private async void StartButton_Click(object sender, RoutedEventArgs e) { int contentLength = await AccessTheWebAsync(); resultsTextBox.Text += String.Format("\r\nLength of the downloaded string: {0}.\r\n", contentLength); } async Task<int> AccessTheWebAsync() { HttpClient client = new HttpClient(); Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com"); DoIndependentWork(); string urlContents = await getStringTask; return urlContents.Length; } void DoIndependentWork() { resultsTextBox.Text += "Working . . . . . . .\r\n"; } }
可以从图中看出,和正常的执行流程是不一样的,我们甚至还会回到“看似已经执行完的方法中”,接着await下面执行
但其实这也不难理解,如果你用过yield return的话。可以看做执行到await的时候(箭头第6步),方法就返回了。等await后面的动作执行完以后我们再回来继续执行下面的步骤。
也就是说,await 表达式标记一个点,在该点上直到等待的异步操作完成方法才能继续。 同时,方法挂起,并且返回到方法的调用方。
值得注意的是,返回并不意味着方法已经执行完成,也就是方法还没退出。await 表达式中异步方法的挂起不能使该方法退出,并且 finally 块不会运行。
但天有不测风云,壮士有可能一去不复返,改一下StartButton_Click代码,看下面的这个情况
1 private void StartButton_Click(object sender, RoutedEventArgs e) 2 { 3 int contentLength = AccessTheWebAsync().Result; 4 5 resultsTextBox.Text += 6 String.Format("\r\nLength of the downloaded string: {0}.\r\n", contentLength); 7 }
我们调用了task的Result,这个属性内部会调用await的方法,也就是会阻塞线程,也就是说线程执行到第3行的时候,就会被阻塞了(阻塞线程是罪大恶极的事情,要牢记在心)。一直等待AccessTheWebAsync()方法完成。
但是AccessTheWebAsync()能完成吗?答案是不能!因为AccessTheWebAsync方法需要依赖这个已经被阻塞的线程回去完成。好吧,一个互相等待的死锁出现了。
你妈妈喊你回家吃饭,快点来接着执行await下面的代码
我靠,我被Result定住了,没办法回去了。
。。。。。。。。
不指定SynchronizationContext
要解决这个问题,就要知道为什么会产生这种情况。
要产生这种情况需要满足两个条件,
1.我指定了要接下来执行下面代码的SynchronizationContext
2.SynchronizationContext里面包含的所有线程被阻塞
SynchronizationContext是什么东西呢?简单来说就是.NET提供的一个类,能让任务在指定的线程里面运行。详细的话看我的这篇多线程之旅七——GUI线程模型,消息的投递(post)与处理(IOS开发前传)
一些SynchronizationContext只封装了单独的一个线程,比如UI thead, 还有一些封装的是一组线程,比如说线程池,可以挑选任意一个线程池的线程来完成指定的任务。
所以说为啥这里悲剧了?因为UI thread所在的SynchronizationContexts只有它这么一个独苗,一旦它被阻塞,就没辙了。
所以解决这种问题的方式有这些,一个就是在await 指定的时候,选择”哥不care你那个线程回来执行,反正来线程啊!“
1 async Task<int> AccessTheWebAsync() 2 { 3 4 HttpClient client = new HttpClient(); 5 6 Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com"); 7 8 DoIndependentWork(); 9 10 string urlContents = await getStringTask.ConfigureAwait(false)
; 11 return urlContents.Length; 12 }
第10行新加的.ConfigureAwait(false) 表达的就是这个意思,也就是我不在乎你接下来执行的线程是什么线程,来自哪个SynchronizationContext。默认是true,也就是我指定就要原来的那个SynchronizationContext,其他的宁可死也不要。
第二种方式就是让它原来的SynchronizationContext本身就封装了多个线程,这样即使阻塞了一个线程,也可以调用其他线程来完成任务。
int contentLength = Task.run(AccessTheWebAsync()).Result;
觉得这样写会改变了原来的调用的话,也可以把这层封装在AccessTheWebAsync方法里,这样外部调用的方式可以保持不变。
async Task<int> AccessTheWebAsync() { return Task.Run(async ()=> {
HttpClient client = new HttpClient(); Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com"); DoIndependentWork(); string urlContents = await getStringTask; return urlContents.Length; }) }
好吧,如果你十分钟没看懂的话,那咱们来看看好玩的yield return,再回来看看这个await。
为什么yield return返回的是IEnumerable<T>类型?就是因为IEnumerable表达的意思是“我只关心你当前的和下一个,我不管你总共有多少”,就和数学归纳法一样,有第一个(n=1),有下一个(n+1),就能推断出所有情况。所以当我们foreach遍历一个IEnumerable类型的时候,无论是在foreach之前就计算好所有元素,还是调用一次MoveNext时才计算并返回一个,都符合这个意思。如果我们的输入序列和输出序列都是用yield return来实现的,那么就相当于构造了一条延迟执行的pipeline。恩,没错,和Linq的实现一样的道理。
C#编译器是通过一个状态机来实现的,每次调用MoveNext方法return 出去之前,都修改当前的状态,使得下次进入的时候是从其他状态进入。
class Foo { public IEnumerable<string> AnIterator() { yield return "1"; yield return "2"; yield return "3"; } } class Program { static void Main() { var collection = new Foo(); foreach (var s in collection.AnIterator()) { Console.WriteLine(s); } } }
通过reflactor查看编译器帮我们做的事情,我们每写一个yield return就对应产生一个分支的case
private bool MoveNext() { switch (this.<>1__state) { case 0: this.<>1__state = -1; this.<>2__current = "1"; this.<>1__state = 1; //修改当前状态为1 return true; case 1: this.<>1__state = -1; this.<>2__current = "2"; this.<>1__state = 2; //修改当前状态为2 return true; case 2: this.<>1__state = -1; this.<>2__current = "3"; this.<>1__state = 3; //修改当前状态为3 return true; case 3: this.<>1__state = -1; break; } return false; }
其实Jeffrey Richter以前就借助过yield return实现的状态机来简化异步编程,使得它看起来像同步编程。
下面的代码yield return 1看起来是不是很像是await?
private static IEnumerator<Int32> PipeServerAsyncEnumerator(AsyncEnumerator ae) { // Each server object performs asynchronous operations on this pipe using (var pipe = new NamedPipeServerStream( "Echo", PipeDirection.InOut, -1, PipeTransmissionMode.Message, PipeOptions.Asynchronous | PipeOptions.WriteThrough)) { // Asynchronously accept a client connection pipe.BeginWaitForConnection(ae.End(), null); yield return 1;
// A client connected, let's accept another client var aeNewClient = new AsyncEnumerator(); aeNewClient.BeginExecute(PipeServerAsyncEnumerator(aeNewClient), aeNewClient.EndExecute); // Accept the client connection pipe.EndWaitForConnection(ae.DequeueAsyncResult()); // Asynchronously read a request from the client Byte[] data = new Byte[1000]; pipe.BeginRead(data, 0, data.Length, ae.End(), null); yield return 1;
// The client sent us a request, process it. Int32 bytesRead = pipe.EndRead(ae.DequeueAsyncResult()); // My sample server just changes all the characters to uppercase // But, you can replace this code with any compute-bound operation data = Encoding.UTF8.GetBytes( Encoding.UTF8.GetString(data, 0, bytesRead).ToUpper().ToCharArray()); // Asynchronously send the response back to the client pipe.BeginWrite(data, 0, data.Length, ae.End(), null);
yield return 1; // The response was sent to the client, close our side of the connection pipe.EndWrite(ae.DequeueAsyncResult()); } // Close happens in a finally block now! }
终于,Jeffrey Richter在C#5里面终于用关键字await更好的实现了他的想法(他去年来上海宣讲的时候说await是他创建的)
有兴趣的话可以看看await创建的状态机长啥样干了什么
一段这样的代码,
private static async Task<String> MyMethodAsync(Int32 argument) { Int32 local = argument; try { Type1 result1 = await Method1Async(); for (Int32 x = 0; x < 3; x++) { Type2 result2 = await Method2Async(); } } catch (Exception) { Console.WriteLine("Catch"); } finally { Console.WriteLine("Finally"); } return "Done"; }
Type1 result1 = await Method1Async(); 经过编译以后,会成这样
// This is the state machine method itself void IAsyncStateMachine.MoveNext() { String result = null; // Task's result value // Compilerinserted try block ensures the state machine’s task completes try { // Assume we're logically leaving the 'try' block // If 1st time in state machine method, // execute start of original method Boolean executeFinally = true; if (m_state == 1) { m_local = m_argument; } // Try block that we had in our original code try { TaskAwaiter<Type1> awaiterType1; TaskAwaiter<Type2> awaiterType2; switch (m_state) { case 1: // Start execution of code in 'try' // Call Method1Async and get its awaiter awaiterType1 = Method1Async().GetAwaiter(); if (!awaiterType1.IsCompleted) { m_state = 0; // 'Method1Async' is completing asynchronously m_awaiterType1 = awaiterType1; // Save the awaiter for when we come back
// Tell awaiter to call MoveNext when operation completes m_builder.AwaitUnsafeOnCompleted(ref awaiterType1, ref this); // The line above invokes awaiterType1's OnCompleted which approximately calls ContinueWith(t => MoveNext()) on the Task being awaited. executeFinally = false; // We're not logically leaving the 'try' block return; // Thread returns to caller } // 'Method1Async' completed synchronously break; case 0: // 'Method1Async' completed asynchronously awaiterType1 = m_awaiterType1; // Restore mostrecent awaiter break; case 1: // 'Method2Async' completed asynchronously awaiterType2 = m_awaiterType2; // Restore mostrecent awaiter goto ForLoopEpilog; } //下面省略的是loop部分和异常处理部分,分开看比较容易看懂
..... } m_builder.SetResult(result); }
恩,实现咋看上去有点复杂,不过注释也已经写得挺明白了,基本上关键就是。
m_builder.AwaitUnsafeOnCompleted(ref awaiterType1, ref this) invokes awaiterType1's OnCompleted which approximately calls ContinueWith(t => MoveNext()) on the Task being awaited.
然后就return出去了,接着在task完成之后再跳转回来。基本原理和上面的类似。
这里是loop部分的代码
// After the first await, we capture the result & start the 'for' loop m_resultType1 = awaiterType1.GetResult(); // Get awaiter's result ForLoopPrologue: m_x = 0; // 'for' loop initialization goto ForLoopBody; // Skip to 'for' loop body ForLoopEpilog: m_resultType2 = awaiterType2.GetResult(); m_x++; // Increment x after each loop iteration // Fall into the 'for' loop’s body ForLoopBody: if (m_x < 3) { // 'for' loop test // Call Method2Async and get its awaiter awaiterType2 = Method2Async().GetAwaiter(); if (!awaiterType2.IsCompleted) { m_state = 1; // 'Method2Async' is completing asynchronously m_awaiterType2 = awaiterType2; // Save the awaiter for when we come back // Tell awaiter to call MoveNext when operation completes m_builder.AwaitUnsafeOnCompleted(ref awaiterType2, ref this); executeFinally = false; // We're not logically leaving the 'try' block return; // Thread returns to caller } // 'Method2Async' completed synchronously goto ForLoopEpilog; // Completed synchronously, loop around }
完整的代码
void IAsyncStateMachine.MoveNext() { String result = null; try { Boolean executeFinally = true; if (m_state == 1) { m_local = m_argument; } try { TaskAwaiter<Type1> awaiterType1; TaskAwaiter<Type2> awaiterType2; switch (m_state) { case 1: awaiterType1 = Method1Async().GetAwaiter(); if (!awaiterType1.IsCompleted) { m_state = 0; m_awaiterType1 = awaiterType1; m_builder.AwaitUnsafeOnCompleted(ref awaiterType1, ref this); executeFinally = false; return; } break; case 0: awaiterType1 = m_awaiterType1; break; case 1: awaiterType2 = m_awaiterType2; goto ForLoopEpilog; } m_resultType1 = awaiterType1.GetResult(); ForLoopPrologue: m_x = 0; goto ForLoopBody; ForLoopEpilog: m_resultType2 = awaiterType2.GetResult(); m_x++; ForLoopBody: if (m_x < 3) { awaiterType2 = Method2Async().GetAwaiter(); if (!awaiterType2.IsCompleted) { m_state = 1; m_awaiterType2 = awaiterType2; m_builder.AwaitUnsafeOnCompleted(ref awaiterType2, ref this); executeFinally = false; return; } goto ForLoopEpilog; } } catch (Exception) { Console.WriteLine("Catch"); } finally { if (executeFinally) { Console.WriteLine("Finally"); } } result = "Done"; } catch (Exception exception) { m_builder.SetException(exception); return; } m_builder.SetResult(result); }
好吧,我承认十分钟学会有点标题党,但是看到这里,我想大家基本都有了个印象吧,目的达到了。