2013-08-24 55 views
3

它工作正常用下面的代码:为什么没有任务<TResult>。结果在这种情况下工作?

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); 
} 

窗体失去响应。如果我用等待,它按预期工作。

更新1: 这两个答案都是正确的,但我只能标记为答案。基于对这个问题的理解,我做了进一步的测试。我用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; 
} 

它的工作对第一次当我点击该按钮,但在第二次点击再次发生死锁。如果我测试(3),这样的后添加ConfigureAwait(假):

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)是必要的。

UPDATE2:

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

回答

3

您正在造成一个死锁,我explain in detail on my blog。总之,await关键字将(默认情况下)在await之前捕获当前上下文,并在该上下文内恢复其余的async方法。在这种情况下,该“上下文”是UI上下文,它在UI线程上执行代码。因此,当您的代码调用Result时,它将阻止UI线程,并且当完成await时,它将无法在UI线程上执行wrap的其余部分。因此,僵局。

避免这种死锁的最好方法是使用await而不是ResultWait来执行异步任务。也就是说,广告的点击方法更改为:

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

这是一个僵局 - 在单线程调度运行两个Task s的等待每个-等来完成。理解这个

两个非常重要的事情:

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

因为这些,你需要特别小心你如何调用一个Task该UI的调度程序内执行拦截的方法。

在第二个示例中,Result的呼叫正在等待wrap中的继续设置完成。延续计划在UI线程上运行,对Result的调用恰好是阻塞的,所以都不会完成。

相关问题