Task.WaitAll保持循环

时间:2012-12-26 21:27:43

标签: c# async-await

我正在尝试使用此异步代码来测试async关键字:

public async Task<string> AsyncMethod()
{
    var link = "http://www.google.com";

    var webclient = new WebClient();
    var result = await webclient.DownloadStringTaskAsync(new Uri(link));

    return result;
}

public async Task<ActionResult> Index()
{
    var a = AsyncMethod();
    var b = AsyncMethod();

    Task.WaitAll(a, b);

    return View();
}

但是当我调试它时,调试器会命中Task.WaitAll并且什么都不做(返回的keywork永远不会被执行)。 如果我在两个AsyncMethod&#39;之前设置等待。并删除Task.WaitAll它的工作原理..那么我做错了什么?

2 个答案:

答案 0 :(得分:9)

因为你的方法看起来像ASP.NET MVC控制器动作,所以我假设你在ASP.NET上运行。

默认情况下,异步方法在挂起的相同上下文(即您调用await的位置)上恢复。在ASP.NET中,这意味着当前的请求上下文。并且一次只能有一个线程处于特定的上下文中。所以,会发生的是执行Index()的线程在请求​​上下文中,在WaitAll()中被阻止。另一方面,AsyncMethod()的两个调用都试图在相同的上下文(完成下载后)之后恢复,但是他们无法这样做,因为Index()仍然在该上下文中执行。因此,这些方法都在deadlock中,因此没有任何反应。

(同样的死锁也会在GUI应用程序中发生,因为GUI上下文在这方面表现相似。控制台应用程序没有这个问题,因为它们没有任何上下文。)

对此的修复是双重的:

  1. 永远不要同步等待async方法。 (可能唯一的例外是如果想要从控制台应用程序的Main()方法执行异步方法。)

    相反,请异步等待它们。在您的情况下,这意味着使用await Task.WhenAll(a, b)

  2. 在“库”方法中使用ConfigureAwait(false)(即那些不需要在请求上下文中执行的方法)。
  3. 使用1或2可以解决您的问题,但如果您同时执行这两项操作可能会更好。

    有关此问题的更多信息,请阅读Stephen Cleary的文章Don't Block on Async Code

答案 1 :(得分:0)

它的工作原理如下:

public Task<string> FakeAsyncMethod()
{
    var link = "http://google.com";
    var webclient = new WebClient();
    var t = new Task<string>(() => webclient.DownloadString(new Uri(link)));
    return t;
}

public async Task Index()
{
    var a = FakeAsyncMethod();
    var b = FakeAsyncMethod();
    a.Start();
    b.Start();
    Task.WaitAll(a, b);
}

async void AsyncCall()
{
    await Index();
}

我不知道为什么它不适用于您的方法,但我怀疑这是因为标有async关键字的方法返回的任务是在运行状态下创建的(更确切地说,使用{{1}等于Status)。我会更多地研究它。

编辑:另一种方法是使用与WaitingForActivation关键字配对的Task.WhenAll

await