繁体   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