简体   繁体   English

来自异步方法的延迟进度报告

[英]Delayed progress reporting from async method

I have a WinForms application containing Button and a RichTextBox controls. 我有一个包含Button和RichTextBox控件的WinForms应用程序。 After user clicks on Button, an IO demanding operation is executed. 用户单击Button后,将执行IO要求操作。 To prevent blocking of the UI thread I have implemented the async/await pattern. 为了防止阻塞UI线程,我实现了async / await模式。 I would also like to report progress of this operation into RichTextBox. 我还想将此操作的进度报告给RichTextBox。 This is how the simplified logic looks like: 这就是简化逻辑的样子:

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);
}

After operation gets successfully completed, the RichTextBox contains following text: 操作成功完成后,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

As you can see the progress for 3rd work item is reported after Done! 如您所见, Done!后报告第3个工作项的进度Done! .

My question is, what is causing the delayed progress reporting and how can I achieve that flow of LoadData_Click will continue only after all progress has been reported? 我的问题是,导致延迟进度报告的原因是什么?只有在报告了所有进度后,我才能实现LoadData_Click流程?

Progress class will capture current synchronization context when created and then will post callbacks to that context (this is stated in documentation of that class, or you can look at source code). Progress类将在创建时捕获当前同步上下文,然后将回调发布到该上下文(这可以在该类的文档中说明,或者您可以查看源代码)。 In your case that means that WindowsFormsSynhronizationContext is captured, and posting to it is rougly the same as doing Control.BeginInvoke() . 在你的情况下,这意味着捕获了WindowsFormsSynhronizationContext ,并且发布到它是与执行Control.BeginInvoke()相同的。

await also captures current context (unless you use ConfigureAwait(false) ) and will post continuation of method to it. await还捕获当前上下文(除非您使用ConfigureAwait(false) )并将方法的继续发布到它。 For iterations except last, UI thread is released on await Task.Delay(500); 对于除last之外的迭代,在await Task.Delay(500);上释放UI线程await Task.Delay(500); and so can process your report callbacks. 因此可以处理您的报告回调。 But on last iteration of your foreach loop the following happens: 但是在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);

So, in last iteration, your callbacks are put into UI thread message queue, but they cannot be executed right now, because you are executing code in UI thread at this same moment. 因此,在上一次迭代中,您的回调被放入UI线程消息队列,但它们现在无法执行,因为您在此同时在UI线程中执行代码。 When code reaches this.Log("done") - it's written to your log control (no BeginInvoke is used here). 当代码到达this.Log("done") - 它被写入你的日志控件(这里没有使用BeginInvoke )。 Then after your LoadData_Click method ends - only at this point UI thread is released from executing your code and message queue may be processed, so your 2 callbacks waiting there are resolved. 然后在您的LoadData_Click方法结束之后 - 仅在此时UI线程被释放执行您的代码并且可能处理消息队列,因此您的2个回调在那里等待解决。

Given all that information - just Log directly as Enigmativity said in comment - there is no need to use Progress class here. 鉴于所有这些信息 - 只需在评论中直接Log为Enigmativity - 这里不需要使用Progress类。

Your code is completely right, the only thing you need is to add 您的代码是完全正确的,您唯一需要的是添加

await Task.Yield(); 

as last sentence of HeavyIO method, just before returning result. 作为HeavyIO方法的最后一句,就在返回结果之前。

The reason is, as previosly said -- you need to allow progress report to be handled by UI thread, and Task.Yield() does exactly that. 原因是,正如先前所说的那样 - 您需要允许UI线程处理进度报告,而Task.Yield()就是这样做的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM