c# - 为什么 Task<TResult>.Result 在这种情况下不起作用?

标签 c# asynchronous

以下代码运行良好:

private void button1_Click(object sender, EventArgs e)
{
    Task<int> t = test(5);
    Console.WriteLine(t.Result);
}

private Task<int> test(int n)
{
    return Task.Run(() => 
    {
        return n;
    });
}

但是如果我用异步方法包装测试方法,它就不起作用:

private Task<int> test(int n)
{
    return Task.Run(() =>
    {
        return n;
    });
}

public async Task<int> wrap()
{
    return await test(5);
}

private void button1_Click(object sender, EventArgs e)
{
    Task<int> t = wrap();
    Console.WriteLine(t.Result);
}

表单失去响应。如果我使用 await,它会按预期工作。

更新 1: 这两个答案都是正确的,但我只能标记一个为答案。基于对这个问题的理解,我做了进一步的测试。我在 wrap 方法中使用 ConfigureAwait 来 让继续在 UI 以外的线程中运行:

public async Task<int> wrap()
{
    return await test(5).ConfigureAwait(false);
}

它工作正常。然后我测试了这个:

public async Task<int> wrap()
{
    int i = await test(5).ConfigureAwait(false);
    int j = i + await test(3);
    return j;
}

当我第一次点击按钮时它正在工作,但在第二次点击时再次死锁。如果我在 test(3) 之后添加了 ConfigureAwait(false),就像这样:

public async Task<int> wrap()
{
    int i = await test(5).ConfigureAwait(false);
    int j = i + await test(3).ConfigureAwait(false);
    return j;
}

它又开始工作了,但这对我来说没有意义。由于第一个 ConfigureAwait(false),wrap() 中的所有后续同步部分都应在非 UI 线程上运行。我不明白为什么需要第二个 ConfigureAwait(false)。

更新2:

private Task<int> test(int n)
{
    return Task.Run(() =>
    {
        Console.WriteLine("test(" + n + "): " + System.Threading.Thread.CurrentThread.ManagedThreadId);
        return n;
    });
}

public async Task<int> wrap()
{
    Console.WriteLine("1.wrap(): " + System.Threading.Thread.CurrentThread.ManagedThreadId);
    int i = await test(5).ConfigureAwait(false);
    Console.WriteLine("2.wrap(): " + System.Threading.Thread.CurrentThread.ManagedThreadId);
    int j = i + await test(3);
    Console.WriteLine("3.wrap(): " + System.Threading.Thread.CurrentThread.ManagedThreadId);
    return j;
}

private void button1_Click(object sender, EventArgs e)
{
    try
    {
        Console.WriteLine("1.button1_Click(): " + System.Threading.Thread.CurrentThread.ManagedThreadId);
        var t = wrap();
        Console.WriteLine("2.button1_Click(): " + System.Threading.Thread.CurrentThread.ManagedThreadId);

        Console.WriteLine(t.Result);    
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }    
}

点击几次后,表单卡住,输出为:

1.button1_Click(): 8
1.wrap(): 8 
test(5): 13
2.wrap(): 8
2.button1_Click(): 8 
test(3): 13

令我惊讶的是,“2.wrap():”与“1.wrap():”在同一个线程中运行,而不是“test(5)”。貌似ConfigureAwait(false)之后的代码也可以跳回UI hread。

最佳答案

这是一个死锁——在单线程调度程序中运行的两个 Task 正在等待彼此完成。

理解这一点有两个非常重要的事情:

  • 默认情况下,await 的结果返回到与它开始时相同的调度程序。
  • UI 在单线程事件循环上运行,在 UI 调用中可用于 await 的即时调度程序是调度此事件循环的调度程序。在此调度程序中一次只能执行一个任务。

由于这些原因,您需要特别注意如何在 UI 调度程序内部执行的 Task 上调用阻塞方法。

在您的第二个示例中,对 Result 的调用正在等待 wrap 中的延续设置完成。延续计划在 UI 线程上运行,对 Result 的调用恰好被阻塞,因此两者都不会完成。

关于c# - 为什么 Task<TResult>.Result 在这种情况下不起作用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18414778/

相关文章:

c# - 如何将管道代码注入(inject)/生成用属性装饰的方法?

c# - 按组重置 RDLC 报告中的页码

node.js - 如何在 async.each 循环中跳出异步系列 - node.js

绑定(bind)对象函数时出现 C++ 异步错误

c# - Enumerable 中的重复 yield 中断

c# - 无效操作异常 : The view 'ViewName' was not found after adding route

c# - 我可以从 Twitter Digits 回调中获取电话号码吗?

javascript - 将 ErrorBoundary 与异步生命周期函数一起使用

c - ReadFileEx/WriteFileEx是否不需要lpCompletionRoutine和使用GetOverlappedResult?

java - java中的多个异步请求处理