WhenAny 在某些情况下表现得像 WhenAll

WhenAny behaving like WhenAll in certain case

所以我遇到了第三方库的问题,即使调用 cancellationToken.Cancel,调用也可能会卡住并且永远不会 return。下面是一个处理这种情况的原型,它是有效的。

public async Task MainAsync()
{
    try
    {
        await StartAsync().ConfigureAwait(false);
    }
    catch (Exception ex)
    {
        Console.WriteLine("Exception thrown");
    }
}

private async Task<string> StartAsync()
{
    var cts = new CancellationTokenSource();

    cts.CancelAfter(3 * 1000);

    var tcs = new TaskCompletionSource<string>();

    cts.Token.Register(() => { Console.WriteLine("Cancelled"); tcs.TrySetCanceled(); });

    return await (await Task.WhenAny(tcs.Task, LongComputationAsync())
        .ConfigureAwait(false)).ConfigureAwait(false);
}

private async Task<string> LongComputationAsync()
{
    await Task.Delay(1 * 60 * 1000).ConfigureAwait(false);

    return "Done";
}

所以 Above 会等待 3 秒,然后它会像它应该的那样抛出一个 TaskCancelledException。 如果您随后将方法 LongComputationAsync 更改为以下内容:

private Task<string> LongComputationAsync()
{
    Task.Delay(1 * 60 * 1000).ConfigureAwait(false).GetAwaiter().GetResult();

    return Task.FromResult("Done");
}

我仍然希望它具有相同的行为,但它的作用是等待整整 1 分钟(在 LongComputationAsync() 中指定),然后抛出 TaskCancelledException

谁能给我解释一下?关于这是如何工作的,或者这是否是正确的开始行为。

Can anyone explain this to me?

当然可以。该问题与 WhenAny 无关。相反,问题在于代码假定同步方法是异步的。

这是一个相对容易犯的错误。但作为一般规则,具有异步签名的方法 可能 是异步的;它没有是异步的。

正如我在我的博客中所描述的,asynchronous methods begin executing synchronously, just like synchronous methods。只有当他们命中 await 时,他们才可能 运行 异步(即便如此,他们也可能会继续同步)。

因此,新版本的 LongCompuationAsync 是同步的,它在将任务返回给 StartAsync 之前执行整个方法,然后将其传递给 WhenAny.