簡體   English   中英

來自異步方法的延遲進度報告

[英]Delayed progress reporting from async method

我有一個包含Button和RichTextBox控件的WinForms應用程序。 用戶單擊Button后,將執行IO要求操作。 為了防止阻塞UI線程,我實現了async / await模式。 我還想將此操作的進度報告給RichTextBox。 這就是簡化邏輯的樣子:

private async void LoadData_Click(Object sender, EventArgs e)
{
    this.LoadDataBtn.Enabled = false;

    IProgress<String> progressHandler = new Progress<String>(p => this.Log(p));

    this.Log("Initiating work...");

    List<Int32> result = await this.HeavyIO(new List<Int32> { 1, 2, 3 }, progressHandler);

    this.Log("Done!");

    this.LoadDataBtn.Enabled = true;
}

private async Task<List<Int32>> HeavyIO(List<Int32> ids, IProgress<String> progress)
{
    List<Int32> result = new List<Int32>();

    foreach (Int32 id in ids)
    {
        progress?.Report("Downloading data for " + id);

        await Task.Delay(500); // Assume that data is downloaded from the web here.

        progress?.Report("Data loaded successfully for " + id);

        Int32 x = id + 1; // Assume some lightweight processing based on downloaded data.

        progress?.Report("Processing succeeded for " + id);

        result.Add(x);
    }

    return result;
}

private void Log(String message)
{
    message += Environment.NewLine;
    this.RichTextBox.AppendText(message);
    Console.Write(message);
}

操作成功完成后,RichTextBox包含以下文本:

Initiating work...
Downloading data for 1
Data loaded successfully for 1
Processing succeeded for 1
Downloading data for 2
Data loaded successfully for 2
Processing succeeded for 2
Downloading data for 3
Done!
Data loaded successfully for 3
Processing succeeded for 3

如您所見, Done!后報告第3個工作項的進度Done!

我的問題是,導致延遲進度報告的原因是什么?只有在報告了所有進度后,我才能實現LoadData_Click流程?

Progress類將在創建時捕獲當前同步上下文,然后將回調發布到該上下文(這可以在該類的文檔中說明,或者您可以查看源代碼)。 在你的情況下,這意味着捕獲了WindowsFormsSynhronizationContext ,並且發布到它是與執行Control.BeginInvoke()相同的。

await還捕獲當前上下文(除非您使用ConfigureAwait(false) )並將方法的繼續發布到它。 對於除last之外的迭代,在await Task.Delay(500);上釋放UI線程await Task.Delay(500); 因此可以處理您的報告回調。 但是在foreach循環的最后一次迭代中會發生以下情況:

// context is captured
await Task.Delay(500); // Assume that data is downloaded from the web here.
// we are now back on UI thread
progress?.Report("Data loaded successfully for " + id);
// this is the same as BeginInvoke - this puts your callback in UI thread
// message queue
Int32 x = id + 1; // Assume some lightweight processing based on downloaded data.
// this also puts callback in UI thread queue and returns
progress?.Report("Processing succeeded for " + id);
result.Add(x);

因此,在上一次迭代中,您的回調被放入UI線程消息隊列,但它們現在無法執行,因為您在此同時在UI線程中執行代碼。 當代碼到達this.Log("done") - 它被寫入你的日志控件(這里沒有使用BeginInvoke )。 然后在您的LoadData_Click方法結束之后 - 僅在此時UI線程被釋放執行您的代碼並且可能處理消息隊列,因此您的2個回調在那里等待解決。

鑒於所有這些信息 - 只需在評論中直接Log為Enigmativity - 這里不需要使用Progress類。

您的代碼是完全正確的,您唯一需要的是添加

await Task.Yield(); 

作為HeavyIO方法的最后一句,就在返回結果之前。

原因是,正如先前所說的那樣 - 您需要允許UI線程處理進度報告,而Task.Yield()就是這樣做的。

暫無
暫無

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

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