Async method blocking on unawaited task

时间:2019-03-19 14:59:37

标签: c# .net asynchronous async-await

In my current project, I have a piece of code that, after simplifying it down to where I'm having issues, looks something like this:

private async Task RunAsync(CancellationToken cancel)
{
    bool finished = false;
    while (!cancel.IsCancellationRequested && !finished)
        finished = await FakeTask();
}

private Task<bool> FakeTask()
{
    return Task.FromResult(false);
}

If I use this code without awaiting, I end up blocking anyway:

// example 1
var task = RunAsync(cancel); // Code blocks here...
... // Other code that could run while RunAsync is doing its thing, but is forced to wait
await task;

// example 2
var task = RunAsync(cancelSource.Token); // Code blocks here...
cancelSource.Cancel(); // Never called

In the actual project, I'm not actually using FakeTask, and there usually will be some Task.Delay I'm awaiting in there, so the code most of the time doesn't actually block, or only for a limited amount of iterations.

In unit testing, however, I'm using a mock object that does pretty much do what FakeTask does, so when I want to see if RunAsync responds to its CancellationToken getting cancelled the way I expect it to, I'm stuck.

I have found I can fix this issue by adding for example await Task.Delay(1) at the top of RunAsync, to force it to truly run asynchronous, but this feels a bit hacky. Are there better alternatives?

4 个答案:

答案 0 :(得分:4)

您对await所做的事情有不正确的心理印象。 await的含义是:

  • 检查等待对象是否完整。如果是,则获取其结果并继续执行协程。
  • 如果未完成,则将当前方法的其余部分注册为awaitable的继续,并通过将控制权返回给调用方来挂起协程。 (请注意,这使它成为半coroutine 。)

在您的程序中,等待的“伪造”总是完整的,因此协程永远不会挂起。

  

还有更好的选择吗?

如果控制流逻辑要求您暂停协程,则使用Task.Yield

答案 1 :(得分:3)

Task.FromResult actually runs synchronously, as would await Task.Delay(0). If you want to actually simulate asynchronous code, call Task.Yield(). That creates an awaitable task that asynchronously yields back to the current context when awaited.

答案 2 :(得分:0)

As @SLaks said, your code will run synchronously. One thing is running async code, and another thing is running parallel code.

If you need to run your code in parallel you can use Task.Run.

class Program
{
    static async Task Main(string[] args)
    {
        var tcs = new CancellationTokenSource();
        var task = Task.Run(() => RunAsync("1", tcs.Token));
        var task2 = Task.Run(() => RunAsync("2", tcs.Token));
        await Task.Delay(1000);
        tcs.Cancel();
        Console.ReadLine();
    }

    private static async Task RunAsync(string source, CancellationToken cancel)
    {
        bool finished = false;
        while (!cancel.IsCancellationRequested && !finished)
            finished = await FakeTask(source);
    }

    private static Task<bool> FakeTask(string source)
    {
        Console.WriteLine(source);
        return Task.FromResult(false);
    }
}

答案 3 :(得分:0)

C#的异步方法同步执行,直到必须等待结果为止。

在您的示例中,方法不必等待结果,因此循环会永远运行并因此阻塞调用方。

插入一个等待Task.Yield()以模拟一些实际的异步工作应该会有所帮助。

相关问题