为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

C#
幕布斯6054654 2022-12-24 09:44:54
我想知道为什么在以下情况下没有继续使用捕获的 GUI 上下文时会出现死锁。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 上下文时会出现死锁?在我的心智模型中,如果有继续,那么它将使用捕获的 GUI 上下文,因此会导致死锁。
查看完整描述

1 回答

?
哆啦的时光机

TA贡献1779条经验 获得超6个赞

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


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


Task DelayAsync()

{

    return Task.Delay(3000);

}

但是,当您将方法标记为 时,事情会变得更加复杂async。方法的一个重要属性async是它处理异常的方式。例如考虑这些方法:


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方法_

可以看到,没有成员直接返回异常。如何知道服务员是否有故障?要知道这一点,您需要调用GetResult将抛出异常的方法。


回到你的例子:


async Task DelayAsync()

{

    await Task.Delay(3000);

}

如果Task.Delay抛出异常,async机器需要将返回任务的状态设置为故障。要知道是否Task.Delay抛出异常,需要在完成GetResult后调用等待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;

}

实际代码比较复杂,用aAsyncTaskMethodBuilder<T>代替了TaskCompletionSource<T>,但是思路是一样的。


查看完整回答
反对 回复 2022-12-24
  • 1 回答
  • 0 关注
  • 55 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信