包装异步方法

时间:2014-02-25 14:01:36

标签: c# asynchronous async-await

我有一个关于在C#中包装异步方法的奇怪行为。

很难删除我的代码中有这种奇怪行为的部分,所以我做了一个测试项目来调查行为,这就是我发现的。

我有一个测试类,它有一个async方法,它只是另一个异步方法的包装器:(在我的原始代码中,它是一个包含2个对象的包装类,它有包装方法)

public class Test
{
    public async Task Delay()
    {
        await Task.Delay(1000);
    }
}

在我的测试程序中,我从异步事件处理程序运行以下代码:(在我的情况下是Loaded事件,因为我使用的是WPF窗口)

var test = new Test();
await Task.Delay(1000);
await test.Delay();
Task.Delay(1000).Wait();
test.Delay().Wait();

一直很好,直到最后一行,永远不会回来。

然后我尝试将测试类更改为以下内容,最后一行有效:

public class Test
{
    public Task Delay()
    {
        return Task.Delay(1000);
    }
}

我的问题是为什么第一种情况不起作用?

2 个答案:

答案 0 :(得分:5)

我详细描述了这个死锁场景on my blogMSDN article

默认情况下,当您await任务时,它会捕获当前的“上下文”(SynchronizationContext.Current,除非它是null,在这种情况下它会捕获TaskScheduler.Current) 。当async方法恢复时,它将在该上下文中恢复。

在您的情况下,将捕获UI上下文,并且您的async方法在延迟完成后尝试在UI线程上继续。但是,它无法继续执行,因为UI线程被阻止等待任务完成。

最好的解决方案是一直使用async“;换句话说,不要阻止async代码。

答案 1 :(得分:4)

通过默认构建await关键字,以尽可能多地保留在原始线程上运行的原始代码(或者,至少在相同的“上下文”中)作为在await之前运行的代码,例如,如果您之前在线程池中运行,则任何线程池线程都会这样做。

正在发生的事情是,所有原始代码在运行时都希望在UI线程上运行。

但是您阻止了Wait中的UI线程,因此永远无法在async方法中运行其余代码,因此完成了外部Task。即在Task.Delay任务完成后,只需要在您的方法中运行这一小段代码:

public class Test
{
    public async Task Delay()
    {
        await Task.Delay(1000);
        //<-- This "Code" needs to run before my Task is completed
    }
}

您可以使用ConfigureAwait(continueOnCapturedContext)请求不会发生这种情况:

  

continueOnCapturedContext

     

true 尝试将继续编组回到捕获的原始上下文;否则, false


请参阅Eric Lippert撰写的旧版blog post,从async新版开始:

  

方法上的“async”修饰符并不意味着“此方法会自动安排在工作线程上异步运行”。这意味着相反;它意味着“此方法包含涉及等待异步操作的控制流,因此将由编译器重写为连续传递样式,以确保异步操作可以在正确的位置恢复此方法。”异步方法的重点你尽可能地留在当前的线程上。