await之后的线程问题

之前看了园子里的一篇文章「async & await的前世今生」,收益颇多。而其中有句话被博主特意用红色标注,所以留意多看了几眼,「await 之后不会开启新的线程(await 从来不会开启新的线程)」。在MSDN上找到的相关资料也佐证了其正确性——The async and await keywords don't cause additional threads to be created. Async methods don't require multithreading because an async method doesn't run on its own thread. The method runs on the current synchronization context and uses time on the thread only when the method is active.(async 和 await 关键字不会导致创建其他线程。 因为异步方法不会在其自身线程上运行,因此它不需要多线程。 只有当方法处于活动状态时,该方法将在当前同步上下文中运行并使用线程上的时间。)

再建立一个Windows Forms应用工程,写点代码更形象地说明问题:

private void Form1_Load(object sender, EventArgs e)
{
    PrintDataAsync();
    Debug.Print("three");
}

private async void PrintDataAsync()
{
    Task<int> result = CalculateDataAsync();
    Debug.Print("second");
    int data = await result;
    Debug.Print("last:" + data);
}

private async Task<int> CalculateDataAsync()
{
    Debug.Print(string.Format("{0} : {1}", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread));
    Debug.Print("first");
    int result = 0;
    for (int i = 0; i < 10; i++)
    {
        result += i;
    }
    await Task.Delay(1000);
    Debug.Print("four");
    Debug.Print(string.Format("{0} : {1}", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread));
    return result;
};

程序的结果如预期的一样,Output窗口中可以看到以下内容:

8 : False
first
second
three
four
8 : False
last:45

await之前的ManagedThreadId值与之后的ManagedThreadId值一致,IsThreadPoolThread始终是False,说明当前线程没有发生改变,也没有产生新的线程。

但如果建立的是Console应用工程,结果就不同了。

static void Main(string[] args)
{
    PrintDataAsync();
    Console.WriteLine("three");
    Console.Read();
}

private static async void PrintDataAsync()
{
    Task<int> result = CalculateDataAsync();
    Console.WriteLine("second");
    int data = await result;
    Console.WriteLine("last:" + data);
}

private static async Task<int> CalculateDataAsync()
{
    Console.WriteLine(string.Format("{0} : {1}", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread));
    Console.WriteLine("first");
    int result = 0;
    for (int i = 0; i < 10; i++)
    {
        result += i;
    }
    await Task.Delay(1000);
    Console.WriteLine("four");
    Console.WriteLine(string.Format("{0} : {1}", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread));
    return result;
}

这段代码的执行结果:

8 : False
first
second
three
four
10 : True
last:45

ManagedThreadId在await之后发生了变化,IsThreadPoolThread也变为了True,说明不是同一个线程。

为什么会这样?再看一下MSDN中描述——「The method runs on the current synchronization context and uses time on the thread only when the method is active」,这里涉及到SynchronizationContext对象的使用。

在Windows Forms工程代码中加入 Debug.Print(SynchronizationContext.Current.ToString()); 检测代码,其输出是System.Windows.Forms.WindowsFormsSynchronizationContext。

而如果在Console工程中加入类似的检测代码 Console.WriteLine(SynchronizationContext.Current.ToString()); 则会抛出空引用异常,因为SynchronizationContext.Current在Console工程中的值为null。

又从MSDN Magazine找到SynchronizationContext相关的文章,其中有介绍到:By convention, if a thread’s current SynchronizationContext is null, then it implicitly has a default SynchronizationContext.(根据惯例,如果一个线程的当前 SynchronizationContext 为 null,那么它隐式具有一个默认 SynchronizationContext。)The default SynchronizationContext is applied to ThreadPool threads unless the code is hosted by ASP.NET.(默认 SynchronizationContext 应用于 ThreadPool 线程,除非代码由 ASP.NET 承载。

这里提到了APS.NET,所以再建个Web Forms应用工程用于验证:

protected void Page_Load(object sender, EventArgs e)
{
    PrintDataAsync();
    Debug.Print("three");
}

private async void PrintDataAsync()
{
    Debug.Print(SynchronizationContext.Current.ToString());
    Task<int> result = CalculateDataAsync();
    Debug.Print("second");
    int data = await result;
    Debug.Print("last:" + data);
}

private async Task<int> CalculateDataAsync()
{
    Debug.Print(string.Format("{0} : {1}", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread));
    Debug.Print("first");
    int result = 0;
    for (int i = 0; i < 10; i++)
    {
        result += i;
    }
    await Task.Delay(1000);
    Debug.Print("four");
    Debug.Print(string.Format("{0} : {1}", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread));
    return result;
}

输出结果:

System.Web.AspNetSynchronizationContext
8 : True
first
second
three
four
9 : True
last:45

ManagedThreadId值发生改变,IsThreadPoolThread始终是True,SynchronizationContext.Current值为System.Web.AspNetSynchronizationContext。

由三次试验及相关资料可以得出结论,await之后的线程依据SynchronizationContext在不同环境中的不同定义而产生不同的结果。所以「await 之后不会开启新的线程(await 从来不会开启新的线程)」的肯定句式改成「await 之后会开启新的线程吗? Maybe」这样的句式更加合适些。

最后补充一点,若是把第一个Windows Forms工程的代码 await Task.Delay(1000); 改成 await Task.Delay(1000).ConfigureAwait(false); 的话,则可以得到第二个Console工程同样的结果。

 

posted @ 2014-09-01 12:24  申江渔夫  阅读(6227)  评论(10编辑  收藏  举报