早上无意中看到了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下面执行

image 

但其实这也不难理解,如果你用过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
   // Compiler­inserted 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 most­recent awaiter break;     case 1: // 'Method2Async' completed asynchronously awaiterType2 = m_awaiterType2; // Restore most­recent 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);

} 
View Code

 

 

好吧,我承认十分钟学会有点标题党,但是看到这里,我想大家基本都有了个印象吧,目的达到了。

posted on 2013-05-13 22:37  一路转圈的雪人  阅读(6474)  评论(10编辑  收藏  举报