[英]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.