为什么避免使用Task.Wait或者Task.Result

目标

  • 简单理解为什么不推荐在调用异步方法时,避免使用Task.Wait,或者Task.Result

示例


private void button1_Click(object sender, EventArgs e)
{
    textBox1.Text = "button1_Click";

    DoSomethingAsync().Wait();

    textBox1.Text = "button1_Click end";
}

public async Task DoSomethingAsync()
{
    await Task.Delay(1000);
    MessageBox.Show("DoSomethingAsyncEnd");
}

private void button2_Click(object sender, EventArgs e)
{
    textBox1.Text = "button2_Click";

    DoSomethingAsync().ConfigureAwait(false);

    textBox1.Text = "button2_Click end";
}


首先我们看上面的button1_Click代码,这种再工作中经常会碰到同事在Web应用中会写这种代码。这种代码在UI程序中会产生上面后果呢?答案是死锁,如下:

"gif1"

第一步我们先点击了button2可以看到并无什么异常,后面会解释,先让我们看第二步,为什么会程序卡死。button1_Click()(同步)等待DoSomethingAsync()完成,同时阻塞了同步上下文(ui主线程),当Task.Delay语句结束时(这里的结束指,遇到await立即返回调用方后),await试图在已经捕获的上下文(ui主线程,遇到await捕捉)中继续运行DoSomethingAsync()的 MessageBox.Show("DoSomethingAsyncEnd"); 但是此时上下文正在等待DoSomethingAsync()完成,此时就会互相等待(这波,这波是我等我自己)加上ui和Asp.Net上下文每次只能执行一个线程,所以就会死锁。

但是在控制台为什么不会死锁呢?如下:


static void Main(string[] args)
{
    // null
    var syncContext = SynchronizationContext.Current;
    var taskSchedulerContext = TaskScheduler.Current;
    DoSomethingAsync().Wait();

}
public static async Task DoSomethingAsync()
{
    await Task.Delay(1000);
    Console.WriteLine("DoSomethingAsyncEnd");
}

因为控制台没有SynchronizationContext上下文,如果没有上下文则会把TaskScheduler当做当前上下文继续运行,可以把这2行代码帖到上面winform里,观察到winform的不为null这个就是UI上下文,除了ui,asp.net上下文,其他很多情况下采用线程池上下文。所以捕获到上下文时候,我们的后续await后续同步的代码都想在原始的上下文中恢复运行,就比如在UI线程中调用DoSomethingAsync(),则这个MessageBox.Show("DoSomethingAsyncEnd");运行在UI线程,但是如果在线程池线程调用DoSomethingAsync(),则await后续同步的代码都在线程池线程上运行,所以需要使用ConfigureAwait(false),这个方法的意思是true尝试将延续任务(await后续同步的代码)送回原始上下文(UI)运行,准确来说是,弃用同步上下文调度器,false相反。

posted @ 2020-12-04 00:37  菜洋  阅读(872)  评论(0编辑  收藏  举报