【问题标题】:Task.WaitAll keeps in loopTask.WaitAll 保持循环
【发布时间】:2012-12-26 21:27:43
【问题描述】:

我正在尝试这个异步代码只是为了测试 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 并且什么也不做(返回键永远不会执行).. 如果我在两个 'AsyncMethod' 之前设置 await 并删除 Task.WaitAll 它可以工作。那么我做错了什么?

【问题讨论】:

  • WaitAll 是否启动任务?如果没有,那么您将等待很长时间,因为没有人启动任务...
  • @flq 你说的开始任务是什么意思?
  • @flq 由带有async 修饰符标记的方法返回的任务已经在运行。
  • 你是否在返回结果处设置了断点?也许在 Task.WaitAll 之前任务已经完成。也尝试在 WaitAll 之后打印结果,看看你是否得到了什么。另一件事,您不需要在 Index() 中使用 async 关键字。
  • 我认为在Task.WaitAll之前它没有完成,下载不能这么快,如果我删除Index的异步我会得到一个异常。

标签: c# async-await


【解决方案1】:

因为您的方法看起来像 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)(即那些实际上不需要在请求上下文中执行的方法)。

使用 1 或 2 可以解决您的问题,但最好同时使用。

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

【讨论】:

  • 非常有用,我注意到这个问题并没有在控制台应用程序上发生,但我不知道为什么。 +1
【解决方案2】:

确保您的async 方法使用CancellationToken

// the cancellation token from the request triggers 
// when the user cancels the HTTP request in the web browser
public async Task<IActionResult> Index(CancellationToken cancellationToken = default)
{
    // internal cancellation token for the timeout
    using var ctsTimeout = new CancellationTokenSource(TimeSpan.FromMilliSeconds(2000));

    // cancels when either the user cancels the request
    // or the timeout expires
    using var cts = CancellationToken.CreateLinkedTokenSource(cancellationToken, ctsTimeout.Token);

    // make sure your methods make use of the cancellation token internally
    // ie. check the token in loops and on I/O requests
    var a = AsyncMethod(cts.Token);
    var b = AsyncMethod(cts.Token);

    // optional: pass the token to the Task.WaitAll method
    // ensures the HTTP request completes
    // even when the internal tasks won't
    Task.WaitAll(new [] { a, b }, cts.Token);

    return View();
}

如果调用的方法之一没有返回,那么超时将确保任务被取消并且Task.WaitAll 抛出Exception

【讨论】:

  • "超时将确保任务被取消" -- 任务不会被取消,它们只是被忽略并成为即发即弃的任务。 CancellationToken 取消任务的等待,而不是任务本身。
  • 这就是 AsyncMethod(cts.Token) 的用途
  • 我说的是Task.WaitAll方法中传递的令牌。我认为那里不需要它,除非你真的想一劳永逸地完成等待的任务。
【解决方案3】:

它是这样工作的:

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 关键字的方法返回的任务是在运行状态下创建的(更准确地说,Status 等于 @ 987654324@)。我会更多地研究它。

编辑:另一种方法是使用 Task.WhenAllawait 关键字配对。

public async Task Index()
{
    var a = AsyncMethod();
    var b = AsyncMethod();
    await Task.WhenAll(a, b);
}

【讨论】:

  • 我不明白为什么我需要启动异步任务,因为当我在 AsyncMethod 中设置本地 url 时,我注意到页面已下载。
  • 太好了,WhenAll 正是我所需要的。谢谢。
  • 在我发布的第一个示例中,您没有使用您的方法,您使用的是我的方法 (FakeAsyncMethod)。那个返回一个Status等于Created但还没有运行的任务——这就是你需要启动它的原因。在我的编辑中,您使用的是您的方法。
  • 这并没有解释实际问题是什么,或者修复工作的原因。而且我不明白为什么让Tasks 处于运行状态会是一个问题。
猜你喜欢
  • 1970-01-01
  • 2013-11-25
  • 1970-01-01
  • 1970-01-01
  • 2013-05-10
  • 2021-03-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多