簡體   English   中英

異步方法阻止未完成的任務

[英]Async method blocking on unawaited task

在我當前的項目中,我有一段代碼,在將其簡化到遇到問題的地方之后,看起來像這樣:

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

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

如果我不等待就使用此代碼,無論如何我最終都會阻塞:

// 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

在實際的項目中,我實際上並沒有使用FakeTask,通常會在其中等待一些Task.Delay,因此大部分時間代碼實際上並沒有阻塞,或者僅進行了有限的迭代。

但是,在單元測試中,我使用的模擬對象執行FakeTask的大部分工作,因此,當我想查看RunAsync是否響應其CancellationToken取消了按我期望的方式取消時,我陷入了困境。

我發現我可以通過await Task.Delay(1)的頂部添加例如await Task.Delay(1)來解決此問題,以強制其真正以異步方式運行,但這感覺有些棘手。 有更好的選擇嗎?

您對await工作有不正確的心理印象。 await的含義是:

  • 檢查等待對象是否完整。 如果是,則獲取其結果並繼續執行協程。
  • 如果尚未完成,則將當前方法的其余部分注冊為awaitable的繼續,並通過將控制權返回給調用方來掛起協程。 (請注意,這使其成為半協程 。)

在您的程序中,等待的“偽造”總是完整的,因此協程永遠不會掛起。

有更好的選擇嗎?

如果您的控制流邏輯要求您暫停協程,則使用Task.Yield

Task.FromResult實際上實際上是同步運行的,就像await Task.Delay(0) 如果要實際模擬異步代碼,請調用Task.Yield() 這將創建一個等待的任務,等待時異步地返回當前上下文。

正如@SLaks所說,您的代碼將同步運行。 一件事正在運行異步代碼,而另一件事正在運行並行代碼。

如果需要並行運行代碼,則可以使用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);
    }
}

C#的異步方法同步執行,直到必須等待結果為止。

在您的示例中,沒有方法必須等待結果的地方,因此循環將永遠運行,從而阻塞了調用者。

插入一個等待Task.Yield()以模擬一些實際的異步工作應該會有所幫助。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM