代码之家  ›  专栏  ›  技术社区  ›  Intellectual Gymnastics Lover

没有继续使用捕获的GUI上下文,但为什么会出现死锁?

  •  0
  • Intellectual Gymnastics Lover  · 技术社区  · 5 年前

    public Form1()
    {
        InitializeComponent();
        CheckForIllegalCrossThreadCalls = true;
    }
    
    async Task DelayAsync()
    {
        // GUI context is captured here (right before the following await)
        await Task.Delay(3000);//.ConfigureAwait(false);
        // As no  code follows the preceding await, there is no continuation that uses the captured GUI context. 
    }
    
    private async void Button1_Click(object sender, EventArgs e)
    {
        Task t = DelayAsync();
    
        t.Wait();
    }
    

    我知道僵局可以通过以下两种方式解决

    • 使用 await Task.Delay(3000).ConfigureAwait(false);
    • t.Wait(); 具有 await t; .

    但这不是问题所在。问题是

    为什么会出现死锁而没有使用捕获的GUI上下文的延续?在我的心智模型中,如果有continuation,那么它将使用捕获的GUI上下文,从而导致死锁。

    0 回复  |  直到 5 年前
        1
  •  4
  •   Kevin Gosse    5 年前

    TL;DR: async 与等待者一起工作,而不是与任务一起工作。因此,在方法的末尾需要一个额外的逻辑位来将等待者的状态转换为任务。


    你认为没有延续的假设是错误的。如果您刚刚返回任务,则是正确的:

    Task DelayAsync()
    {
        return Task.Delay(3000);
    }
    

    但是,如果将方法标记为 异步 . 一个重要的属性

    Task NoAsync()
    {
        throw new Exception();
    }
    
    async Task Async()
    {
        throw new Exception();
    }
    

    var task1 = NoAsync(); // Throws an exception
    var task2 = Async(); // Returns a faulted task
    

    区别在于异步版本将异常包装在返回的任务中。

    当你 await 编译器实际调用的方法 GetAwaiter() 在你等待的物体上。等待者定义了3个成员:

    • 这个 IsCompleted 财产
    • 这个 OnCompleted 方法
    • 这个 GetResult 方法

    获取结果 方法,该方法将引发异常。

    回到你的例子:

    async Task DelayAsync()
    {
        await Task.Delay(3000);
    }
    

    如果 Task.Delay 抛出异常 异步 机器需要将返回任务的状态设置为故障。想知道 任务。延迟 抛出异常,需要调用 获取结果 任务。延迟 已完成。因此,您有一个延续,尽管在看到代码时它并不明显。在引擎盖下,异步方法如下所示:

    Task DelayAsync()
    {
        var tcs = new TaskCompletionSource<object>();
    
        try
        {
            var awaiter = Task.Delay(3000).GetAwaiter();
    
            awaiter.OnCompleted(() =>
            {
                // This is the continuation that causes your deadlock
                try
                {
                    awaiter.GetResult();
                    tcs.SetResult(null);
                }
                catch (Exception ex)
                {
                    tcs.SetException(ex);
                }
            });
        }
        catch (Exception ex)
        {
            tcs.SetException(ex);
        }
    
        return tcs.Task;
    }
    

    实际的代码更复杂,并且使用 AsyncTaskMethodBuilder<T> TaskCompletionSource<T> ,但想法是一样的。