3

我正在尝试这个异步代码只是为了测试 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'之前设置等待并删除Task.WaitAll它就可以了..那么我做错了什么?

4

3 回答 3

12

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

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

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

对此的修复是双重的:

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

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

  2. 在您的“库”方法中使用ConfigureAwait(false)(即那些实际上不需要在请求上下文中执行的方法)。

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

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

于 2012-12-26T22:30:37.960 回答
-1

确保您的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.

于 2022-01-07T10:09:53.970 回答
-2

它是这样工作的:

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等于WaitingForActivation)。我会更多地研究它。

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

public async Task Index()
{
    var a = AsyncMethod();
    var b = AsyncMethod();
    await Task.WhenAll(a, b);
}
于 2012-12-26T21:53:07.440 回答