我有一个包含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个工作项的进度。
我的问题是,导致延迟进度报告的原因是什么?只有在报告了所有进展后,我才能实现LoadData_Click
的流量?
答案 0 :(得分:5)
Progress
类将在创建时捕获当前同步上下文,然后将回调发布到该上下文(这可以在该类的文档中说明,或者您可以查看源代码)。在您的情况下,这意味着捕获了WindowsFormsSynhronizationContext
,并且发布到它就像执行Control.BeginInvoke()
一样粗糙。
await
还捕获当前上下文(除非您使用ConfigureAwait(false)
)并将方法的继续发布到它。对于除last之外的迭代,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
课程。
答案 1 :(得分:1)
您的代码是完全正确的,只需添加
interpret-as="telephone"
作为HeavyIO方法的最后一句话,即将返回结果。
原因如前所述-您需要允许进度报告由UI线程处理,而Task.Yield()正是这样做的。