×

Loading...

传统的异步方式也可不乱

本文发表在 rolia.net 枫下论坛WebClient client = null;
using (client = new WebClient())
{
try
{
byte[] data = null;
client.DownloadDataAsync(new Uri(file1));
client.DownloadDataCompleted += (s, e) =>
{
data = e.Result;
if (data != null)
{
// Process data from file1
}
};
}
catch (WebException ex)
{

}
catch (Exception ex)
{

}

using (client = new WebClient())
{
try
{
byte[] data = null;
client.DownloadDataAsync(new Uri(file2));
client.DownloadDataCompleted += delegate(object sender, DownloadDataCompletedEventArgs e)
{
data = e.Result;
// Process data from file2
};
}
catch (WebException ex)
{

}
catch (WebException ex)
{

}
}
}更多精彩文章及讨论,请光临枫下论坛 rolia.net
Sign in and Reply Report

Replies, comments and Discussions:

  • 工作学习 / 科技领域杂谈 / 介绍一下.net 4.5中新的异步模式 async 和 await
    为了简化异步编程,.net 4.5引入了两个新的关键字async和await,使得异步编程大大简化,代码可读性和可维护性成倍提高,不过代价总是有,就是程序员必须明白.net 4.0中引入的Task概念,没有Task基础知识,使用async和await会感觉一头雾水。就好像不明白delegate、generic而去强行使用LINQ一样。 Task的出现是为了使多线程编程变得简单,Task和Thread还不是一回事,Task由TaskManager管理,一个Task会执行在一个Thread上,但是如果没有足够的Thread,TaskManager会暂时挂起这个Task,直到有足够的Thread资源为止。Task的另外一个好处是TaskManager会把执行这些Task的Thread自动的分配在不同的Core上,就是所谓的并行编程,拿我的i7来说,有4个物理Core。如果有4个Task,那么.net TaskManager会在4个Core上建立4个独立的Thread,然后把4个Task放到这4个Thread运行,有点像绕口令,这都是MSDN说的,我也不知道真假,也没测过,估计是真的吧

    .net 4.0以后新出现的并行编程、异步编程框架都是基于Task,前段时间出现的Reactive Extention(Rx)也是如此。async和await和Rx有一些功能重复,Rx先按下不表,以后再说。

    用一个例子介绍一下async和await,这是前段时间我再给windows phone 8写app时的时候实际用到的。在没有async和await之前,类似的win phone代码简直乱得一团糟,写完了都不想再看第二眼(很好奇iPhone和安卓是怎样处理类似情况的?),现在写完了很想再看第二眼:

    project: 根据email and password得到用户权限userRight,再根据userRight列出这个user能看到的所有电影movieList,

    先把代码放出,一个是同步,一个是async + await 异步

    static void Main(string[] args)
    {
        Console.WriteLine("main thread started..");
        
        getUserMoves("me@hotmail.com", "password");
        getUserMoviesTaskAsync("me@hotmail.com","password");
    
        Console.WriteLine("waiting for main thread to end");
        Console.ReadLine();
    }
    
    private static void getUserMoves(string email, string password)
    {
        WebClient wb = new WebClient();
        string userRight = wb.DownloadString(email + password);
        string moviesUserCanWatch = wb.DownloadString(userRight);
        Console.WriteLine(moviesUserCanWatch);
    }
    
    private static async void getUserMoviesTaskAsync(string email, string password)
    {
        WebClient wb = new WebClient();
        string userRight = await wb.DownloadStringTaskAsync(email + password);
        string moviesUserCanWatch = await wb.DownloadStringTaskAsync(userRight);
        Console.WriteLine(moviesUserCanWatch);
    }
    
    第一个
    getUserMoves("me@hotmail.com", "password");
    是同步代码,也就是说除非里面所有步骤都执行完毕,否则主线程会被block,你永远也看不到“waiting for main thread to end”


    第二个
    getUserMoviesTaskAsync("me@hotmail.com","password");
    是异步代码,程序会输出:
    main thread started..
    waiting for main thread to end
    然后才会输出 downloaded string,主线程不会被block

    写法极其相似,达到同样效果,但是一个是同步,一个是异步,异步的代码当然可以有其它多种实现方法,但是.net 4.5的这种async + await写法是最简洁的 大概讲一下原理:

    一旦一个函数被前面有async关键字,比如:
    async void getUserMoviesTaskAsync
    那么.net 在执行到这个函数的时候会像对待普通的函数一样去运行这个函数内的语句,同时主线程被block,等待getUserMoviesTaskAsync结束

    在执行 getUserMoviesTaskAsync 函数内的语句的时候,一旦遇到await关键字,比如:
    await wb.DownloadStringTaskAsync(email + password);
    .net会建立另外的一个Thread去运行wb.DownloadStringTaskAsync(email + password)
    同时把运行权交还给调用getUserMoviesTaskAsync函数的Object,在这里,就是Main(string[] args) {}啦,于是main继续运行输出:
    “waiting for main thread to end”
    当wb.DownloadStringTaskAsync(email + password)运行结束,.net会把运行权切换到getUserMoviesTaskAsync函数,让它继续运行,直到碰到下一个await,这时再次把运行权切换到主线程,然后再返回getUserMoviesTaskAsync函数,直到getUserMoviesTaskAsync函数运行结束,返回主线程
    • 一個不open的技術就不要在這露一手了,末沙益思
      • 啥叫“open的技術”,.net所有的源代码你要是想看用Reflector都可以看啊?露一手的目的是要抛砖引玉,吸引同行发表更精彩的文章,否则怎么进步嘛?
    • 多线程编程的主要难点还不是在运行权上,而是如果多线程需要对统一资源进行访问的时候的处理,如何轻松的解决优先权和deadlock的问题。
    • 看上去又是一个I/O Completion Port的遣生品。Android上类似的是AsyncTask,在系统控制的线程池里执行任务,然后在原始界面线程中将结果传回(Android不允许在界面线程之外操作界面元素)。
      • .net 同样不允许,WPF,Silverlight, Windows Phone 都不允许,否则会抛出“Cross Thread xxx” Exception。不过有变通的方法,可以用Dispacher.BeginInvoke(Action a)来操作UI线程的Object
        • 一个初学者顶一下。看不大懂,留着慢慢琢磨。
          • 没关系,谁不是从初学者走过来的,豆泡松果和我的跟贴说的UI跨线程异常,我用一个小程序解释一下:
            如果有一段WPF程序:

            XAML:

            <Window x:Class="WpfApplication1.Window1"
                    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    Title="Window1" Height="300" Width="300">
                <Grid>
                    <Button Content="Button" HorizontalAlignment="Left" Margin="34,30,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click_1" x:Name="buton1"/>
                    <TextBlock HorizontalAlignment="Left" Margin="58,90,0,0" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Top" x:Name="textBlock1"/>
                </Grid>
            </Window>



            Code Behind:

                    private void Button_Click_1(object sender, RoutedEventArgs e)
                    {
                        this.textBlock1.Text = "hello world!";
                    }


            逻辑很简单,这段程序在点击Button之后TextBlock会显示“hellow world!”,但是如果我把程序用Task变一下,然它变成异步模式执行:

                    private void Button_Click_1(object sender, RoutedEventArgs e)
                    {
                        Task.Factory.StartNew(() => this.textBlock1.Text = "hello world!");
                    }


            就会出现跨线程操作异常,因为
            Task.Factory.StartNew(Action a) 会创建一个Task,这个Task运行在另外一个不同于UI的线程上。解决方法也很简单,就是我提到的用Dispatcher.BeginInvoke

                    private void Button_Click_1(object sender, RoutedEventArgs e)
                    {
                        Task.Factory.StartNew(() => 
                            this.buton1.Dispatcher.BeginInvoke(new Action(() => this.textBlock1.Text = "hello world!")));
                    }


            That is..
          • hi,还在渥太华吗?
        • 有了这些await async什么的就不用担心 UI Thread update的问题, 内部它会利用synchronizationcontext把它转移到 ui thread. 这方便很多 很不小心很容易dead lock. 太多的abstraction也不好
          • 对对,这一点是async await的另外一个巨大好处,在WPF或者windows phone编程中可以直接操作UI线程的Object,帖子中忘了写了
    • Nice post
      Though not a .net guy, I still can feel the topic is well written and explained.
    • 写的很好,顶一下!
      • 多谢多谢,其实要解释清楚async和await的原理一个帖子很难,只能是点到为止。只是因为这两个关键字实在是让我的代码简洁了太多,所以不得不推荐一下
        • 那你能不能写个简单程序, 用两种方法各写一次。 比较说明一下简化在哪里?
          然后, 再说明一下, 程序复杂到什么规模上, 新的关键词可以有效简化程序?
          • 好好,容我忙完手头的活,想一个Demo
            • 这才是大拿风范
              • 嗯,写完了,大拿可不敢当,这里藏龙卧虎的大拿不知有多少,我就是混口饭吃。加一些注释,一会发上来,应该很清楚地看出async await的优势
          • 一个WPF Demo Project,显示了Async + Await的优势
            Project的目的很简单,根据 http://rolia.net 找到论坛入口URI =>从所有论坛中找到“科技领域杂谈”URI=> 在“科技领域杂谈”帖子中找到我的第一个帖子URI=> 显示我的帖子的原始HTML信息

            每一步都是一个单独的异步操作,每一步都依靠上一步操作的结果

            比较费时间的是根据原始的HTML Parse出需要的URI,不过如果你对LINQ很熟悉,这也就是分分钟的事情,不过在这个程序里这一块是凑或完成的,因为是为了演示async + await异步操作,这一步不是重点

            这是一个WPF的Project,自己创建一个WPF的Project,分别把XAML和Code Behind Copy进去就好,直接可以运行。第一次运行整个window会卡住,我也不知道为什么,可能是WebClient在后台建立Socket连接,但是第二次、第三次就好了。在windows phone 里我没遇到过这种情况

            第一个按钮是传统的异步方式,第二个按钮是Async+Await方式

            XAML:
            <Window x:Class="WpfApplication1.Window1"
                    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    Title="Window1" Height="300" Width="300">
                <StackPanel>
                    <Button Content="Go to Rolia, Normal Async Usage" x:Name="butonNormalAsync" Click="butonNormalAsync_Click"/>
                    <Button Content="Go to Rolia, Async + Await" x:Name="buttonAsyncAwait" Click="buttonAsyncAwait_Click"/>
                    <TextBox x:Name="textBlock1" Height="200" TextWrapping="Wrap" ScrollViewer.HorizontalScrollBarVisibility="Visible"/>
                </StackPanel>
            </Window>
            


            Code Behind:
            using System;
            using System.Collections.Generic;
            using System.Linq;
            using System.Net;
            using System.Text;
            using System.Threading.Tasks;
            using System.Windows;
            using System.Windows.Controls;
            using System.Windows.Data;
            using System.Windows.Documents;
            using System.Windows.Input;
            using System.Windows.Media;
            using System.Windows.Media.Imaging;
            using System.Windows.Shapes;
            
            namespace WpfApplication1
            {
                /// <summary>
                /// Interaction logic for Window1.xaml
                /// </summary>
                public partial class Window1 : Window
                {
                    public Window1()
                    {
                        InitializeComponent();
                    }
            
                    string baseForumURI = "http://www.rolia.net"; // 论坛入口的URI
                    string forumEntryURI; // should be http://www.rolia.net/f // 论坛URI
                    string scienceForumEntryURI; // 科技领域杂谈的URI
                    string firstThreadURL; // 我的第一篇帖子的URI
            
                    string htmlString;
            
            
                    // 传统的异步方式,就一个字:乱
                    private void butonNormalAsync_Click(object sender, RoutedEventArgs e)
                    {            
                        WebClient wc = new WebClient();
                        wc.DownloadDataCompleted += (s, e1) =>
                            {
                                htmlString = System.Text.Encoding.GetEncoding("UTF-8").GetString(e1.Result);
                                forumEntryURI = getForumEntryURI(htmlString);
            
                                WebClient wc1 = new WebClient();
                                wc1.DownloadDataCompleted += (s2, e2) =>
                                    {
                                        htmlString = System.Text.Encoding.GetEncoding("UTF-8").GetString(e2.Result);
                                        scienceForumEntryURI = getScientForumEntry(htmlString);
            
                                        WebClient wc2 = new WebClient();
                                        wc2.DownloadDataCompleted += (s3, e3) =>
                                            {
                                                htmlString = System.Text.Encoding.GetEncoding("UTF-8").GetString(e3.Result);
                                                firstThreadURL = getFirstThreadURL(htmlString);
                                          
                                                //ok now, we came to the last step: show the content of my thread
                                                WebClient wc3 = new WebClient();
                                                wc3.DownloadDataCompleted += (s4, e4) =>
                                                    {
                                                        htmlString = System.Text.Encoding.GetEncoding("UTF-8").GetString(e4.Result);
                                                        buttonAsyncAwait.Dispatcher.BeginInvoke(new Action(() => textBlock1.Text = htmlString));
                                                    };
                                                //从我的第一篇帖子的URI download content
                                                wc3.DownloadDataAsync(new Uri(firstThreadURL));
                                            };
                                        // step 3: 从“科技领域杂谈” 找到我的第一篇帖子
                                        wc2.DownloadDataAsync(new Uri(scienceForumEntryURI));
                                    };
                                // step 2: 从论坛列表找到“科技领域杂谈”
                                wc1.DownloadDataAsync(new Uri(forumEntryURI));
                            };
                        // step 1: 从rolia.net找到论坛入口
                        wc.DownloadDataAsync(new Uri(baseForumURI));
                    }
            
                    // Async + Await 异步方式
                    private async void buttonAsyncAwait_Click(object sender, RoutedEventArgs e)
                    {
                        WebClient wc = new WebClient();
            
                        // step 1: 从rolia.net找到论坛入口
                        byte[] resultBytes = await wc.DownloadDataTaskAsync(baseForumURI);
                        htmlString = System.Text.Encoding.GetEncoding("UTF-8").GetString(resultBytes);
                        forumEntryURI = getForumEntryURI(htmlString);
            
                        // step 2: 从论坛列表找到“科技领域杂谈”
                        resultBytes = await wc.DownloadDataTaskAsync(forumEntryURI);
                        htmlString = System.Text.Encoding.GetEncoding("UTF-8").GetString(resultBytes);
                        scienceForumEntryURI = getScientForumEntry(htmlString);
            
                        // step 3: 从“科技领域杂谈” 找到我的第一篇帖子
                        resultBytes = await wc.DownloadDataTaskAsync(scienceForumEntryURI);
                        htmlString = System.Text.Encoding.GetEncoding("UTF-8").GetString(resultBytes);
                        firstThreadURL = getFirstThreadURL(htmlString);
            
                        //从我的第一篇帖子的URI download content
                        resultBytes = await wc.DownloadDataTaskAsync(firstThreadURL);
                        htmlString = System.Text.Encoding.GetEncoding("UTF-8").GetString(resultBytes);
            
                        // show the raw content of my thread in the textBox
                        textBlock1.Text = htmlString;
                    }
            
                    // parse HTML to ForumEntryURI
                    string getForumEntryURI(string htmlString)
                    {
                        int index1 = htmlString.IndexOf("枫下论坛");
                        int index2 = htmlString.Substring(0,index1).LastIndexOf(@"<a");
                        string result = htmlString.Substring(index2, index1 - index2);
                        result = result.Split(' ').First(s => s.Contains("href")).TrimEnd('>','\n','\t').Split('=').Last().Trim('\"');
                        return string.Format("{0}{1}",baseForumURI, result);
                    }
            
                    // parse HTML to ScientForumEntry
                    private string getScientForumEntry(string htmlString)
                    {
                        int index1 = htmlString.IndexOf("科技领域杂谈");
                        int index2 = htmlString.Substring(0, index1).LastIndexOf(@"<a");
                        string result = htmlString.Substring(index2, index1 - index2);
                        result = result.Split(' ').First(s => s.Contains("href")).Substring("href=".Length).Trim('\'');
                        return string.Format("{0}/{1}", forumEntryURI, result);
                    }
            
                    // parse HTML to FirstThreadURL
                    private string getFirstThreadURL(string htmlString)
                    {
                        int index1 = htmlString.IndexOf("binghongcha76");
                        index1 = htmlString.Substring(0, index1).LastIndexOf(@"<a");
                        int index2 = htmlString.Substring(0, index1).LastIndexOf(@"<a");
            
                        string result = htmlString.Substring(index2, index1 - index2);
                        result = result.Split(' ').First(s => s.Contains("href")).Substring("href=".Length).Trim('\'');
                        return string.Format("{0}/{1}", forumEntryURI, result);
                    }
                }
            }
            • 这个是内行才能如此思路清晰。
            • 慢点。 我有问题老大。 你这个4步, 每个都必须等前一步完成, 才能进行下一步。 -- 问题是, 为什么要进行异步操作?
              • 因为不能Block UI线程啊,用同步的话UI就Froze了
                • 明白了。 如果是后台批处理就不要这么麻烦了。
                • 还有, 一般的网页也不用这么复杂的异步编程。 先给用户一个对话框,告诉他等, 然后用ajax call, 完成了就告诉用户有结果了。。。。
            • 传统的异步方式也可不乱
              本文发表在 rolia.net 枫下论坛WebClient client = null;
              using (client = new WebClient())
              {
              try
              {
              byte[] data = null;
              client.DownloadDataAsync(new Uri(file1));
              client.DownloadDataCompleted += (s, e) =>
              {
              data = e.Result;
              if (data != null)
              {
              // Process data from file1
              }
              };
              }
              catch (WebException ex)
              {

              }
              catch (Exception ex)
              {

              }

              using (client = new WebClient())
              {
              try
              {
              byte[] data = null;
              client.DownloadDataAsync(new Uri(file2));
              client.DownloadDataCompleted += delegate(object sender, DownloadDataCompletedEventArgs e)
              {
              data = e.Result;
              // Process data from file2
              };
              }
              catch (WebException ex)
              {

              }
              catch (WebException ex)
              {

              }
              }
              }更多精彩文章及讨论,请光临枫下论坛 rolia.net
          • 我的那个例子并不算复杂,只有3、4个callback action 需要处理,看看这个例子,那才叫头疼
        • 我应该谢谢你才对。我原来对async和await的理解有误,毕竟对C#和.NET没怎么动手。看了你的帖子,又查了查MSDN,确实你写的对。很高兴读帖学到有用的东西。:)
          • ^_^,happy to see that,有用就好,过几天如果心情好再写篇怎样使用Rx的文章,看看Rx是怎样简化异步编程的。.net 的这些新技术,好用是好用,就是入门的门槛越来越高,需要很多基础知识的堆积
            • 这是好事情。。。。, 符合经验不可传授原理
    • 赞一个!
    • 大猫,有个问题请教:如果你在UI线程里调用await,这会不会导致UI线程被堵塞在这里(等待任务异步完成)。这在Android里有可能导致ANR。在你的第一个例子里main函数没有await getUserMoviesTaskAsync,
      有没有可能getUserMoviesTaskAsync真正完成时main函数已经执行完毕并且退出?

      Android通常的做法是使用AsynTask,它在异步线程完成任务后会在调用线程(一般是UI线程)的消息队列里发送一个消息,UI线程的looper负责在UI线程环境里处理所有的消息(跟Win32的消息队列概念一样)。

      顺便提一下,微软Windows 8 ADK里添加了很多测试项目,其中一个就是应用程序必须在一定时间(5秒)内响应消息,否则测试失败,跟Android的ANR一样。
      • 不太可能发生这种情况,因为await必须和async同时使用,await必须存在于被async标示的函数里面,这是语法要求
        async完整的签名是:

        async Task<T> methodName(.....)
        {
        await taskAsyncMethod();
        }

        或者

        async void methodName(.....)
        {
        await taskAsyncMethod();
        }

        UI线程的函数一般都是

        void Main(args[] args)
        { .... }

        不包含async关键字,不可能在里面使用await.

        有没有可能getUserMoviesTaskAsync真正完成时main函数已经执行完毕并且退出?
        有可能,所以我用了一个Console.ReadLine();等待,如果main退出了程序也就结束了
        • 那么你的那个WFP Demo Project的例子(#8164560@0),buttonAsyncAwait_Click这个函数是在UI线程中调用的吗?如果是,如何保证不堵赛UI线程?如果不是,如何处理异步完成时UI界面已经转移?
          另外还是以你开头的代码为例子,如果程序是这个样子的话:
          static void Main(string[] args)
          {
              Console.WriteLine("main thread started..");
              
              getUserMoves("me@hotmail.com", "password");
              getUserMoviesTaskAsync("me@hotmail.com","password");
          
              Console.WriteLine("waiting for main thread to end");
              int n = 1;
              while(true)
              {
                  Console.WriteLine(GetCurrentThreadId() + ": n= " + n);
                  n=n+1;
              }
          }
          private static async void getUserMoviesTaskAsync(string email, string password)
          {
              WebClient wb = new WebClient();
              string userRight = await wb.DownloadStringTaskAsync(email + password);
              Console.WriteLine(GetCurrentThreadId() + " first await finished, about to run next one");
              string moviesUserCanWatch = await wb.DownloadStringTaskAsync(userRight);
              Console.WriteLine(moviesUserCanWatch);
          }
          
          “一旦遇到await关键字...把运行权交还给调用getUserMoviesTaskAsync函数的Object,在这里,就是Main(string[] args) {}”的具体意思是什么?比如说main正在原始线程中执行一个循环;.net在工作线程完成getUserMoviesTaskAsync,它怎样把控制权交给main?在控制台会看到什么?getUserMoviesTaskAsync中的Console.WriteLine打印出的线程id是哪一个线程的?
          • 嗯,先回答第一个:在WPF Demo中,当鼠标点击第二个按钮之后:
            async void buttonAsyncAwait_Click(object sender, RoutedEventArgs e) 被调用,这个时候UI线程被Block,直到程序运行到
            byte[] resultBytes = await wc.DownloadDataTaskAsync(baseForumURI);,这个时候.net会建立另外一个线程运行 wc.DownloadDataTaskAsync(baseForumURI) 所返回来的Task<byte[]> object,同时运行权被返回 调用 async void buttonAsyncAwait_Click(object sender, RoutedEventArgs e) 的函数,这个函数就是UI的线程,在这个时候,UI被释放,不再Block

            直到Task<byte[]> object运行完毕,系统把运行权又一次返回async void buttonAsyncAwait_Click(object sender, RoutedEventArgs e)函数,把结果赋值给 byte[] resultBytes
            • "直到Task<byte[]> object运行完毕,系统把运行权又一次返回async void buttonAsyncAwait_Click":对这句话不是很理解:
              在第一个await时,系统在工作线程执行网络操作,UI线程返回main函数接着执行其它代码,甚至会调用其它函数函数; 当网络操作完成时,系统需要执行await之后的代码,这个时候系统在哪一个线程里执行await之后的代码?如果是UI线程,也就是说系统会强行修改栈和把指令寄存器直接设到某个函数的中间?如果你在await之后的第一条语句上设个断点,当它命中时的函数调用栈是什么样子的?
              • 这个,有点晕啊,寄存器,堆什么的只是略知一二,实在是不知该怎么回答。推荐看看MSDN的这篇文章,很好地解释了每一步的具体工作状态
                • I think finally I figured out how it works and most importantly, how it is implemented, please see post @8171243.
    • 什么.net task manager? 叫thread pool吧
      • 嗯,可能是,我是凭记忆写的,难免表述有错,欢迎指正
      • 确切的说是 Task queue 和 Thread pool 结合做的。
    • 我是学C++的,所以思维总是从硬件线程的角度出发。经过仔细阅读若干篇MSDN文章,终于明白了async/await的工作原理(从Win32和硬件线程的角度):
      本文发表在 rolia.net 枫下论坛如果一个.net函数有async关键字,编译器会自动为其生成一个数据结构,这个数据结构包含函数内部变量以及引用的局部变量等信息;同时编译器为这个函数生成另外一个或者两个更为复杂的函数,新生成的函数利用之前提到的数据结构来记录这个async函数执行到了那里,在等待什么,以及相关的数据(例如局部变量的值)。新生成的函数和数据结构被称为“状态机”,这个状态机允许一个函数被分成若干次运行,在每次运行时利用状态机的数据来从上次运行停止的地方接着运行,一般是await的下一条指令。

      当一个async函数里执行await时,.net会在状态机里记录下当先执行到了那里,当前局部变量的值,在另外的工作线程中开始异步操作,然后将当前线程的控制权返回到调用者。当工作线程的异步操作完成时,.net会在“某个”synchronization context里post一个消息,当运行这个synchronization context的线程中的消息循环处理这个消息时,它会找到相应的状态机,调用相关的函数回复这个状态机的运行。如果async函数里有多个await时,.net会重复这个步骤。

      前面提到“某个”synchronization context,如果调用await时的线程是UI线程,那么.net默认在UI线程恢复执行状态机;否则有可能在完成异步操作的线程中恢复执行虚拟机。

      简单的说,.net编译器会自动产生一些数据和额外的代码构成一个能够让函数分多步完成的虚拟机,以及相应的调用虚拟机的机制,这就是asnyc/await的背后实现原理,编译器承担了几乎全部的背后工作。更多精彩文章及讨论,请光临枫下论坛 rolia.net
      • 多谢多谢,长见识了。我倒是不会考虑这么深,只是达到会用就好。我对await的理解是:因为await后面跟着的函数必须是Task<T>,同时Task<T>允许使用ContinueWith(Action a)来继续,所以我的理解就是.net把一个async函数分解成了足够多的ContinueWith(Action),一段一段的执行
        • 好象不对。异步的不只是await的部分。在你的例子里,去掉await, wb.DownloadStringTaskAsync(。。。)也会异步运行。
    • A good article about how to be careful when using async/await: "Async in C# and F#: Asynchronous gotchas in C#"
      • My initial misunderstanding was exactly the Gotcha #1. I still don't quite see the reason behind this model choice. To me, the current C# model is neither intuitive to develope nor easy to implement. Is it a feature or a mistake?