3

使用以下代码可以正常工作:

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线程。</p>

4

2 回答 2

4

It's a deadlock -- two Tasks running in a single-threaded scheduler are waiting for each-other to complete.

Two very important things to understanding this:

  • By default, the result of an await comes back on the same scheduler as it started in.
  • The UI runs on a single-threaded event loop, and the immediate scheduler available to await in UI calls is one which dispatches onto this event loop. Only a single Task can be executing at once in this scheduler.

Because of these, you need to be especially careful about how you call blocking methods on a Task which executes inside of the UI's scheduler.

In your second example, the call to Result is waiting for the continuation setup in wrap to complete. The continuation is scheduled to be run on the UI thread, which the call to Result just happens to be blocking, so neither will ever complete.

于 2013-08-24T04:27:21.683 回答
3

You are causing a deadlock which I explain in detail on my blog. In summary, the await keyword will (by default) capture the current context before the await and resume the rest of the async method within that context. In this case, that "context" is the UI context, which executes code on the UI thread. So, when your code calls Result, it is blocking the UI thread, and when the await completes, it can't execute the rest of wrap on the UI thread. Thus, deadlock.

The best way to avoid this deadlock is to use await instead of Result or Wait for asynchronous tasks. I.e., change your click method to:

private async void button1_Click(object sender, EventArgs e)
{
  Task<int> t = wrap();
  Console.WriteLine(await t);
}
于 2013-08-24T04:27:00.437 回答