c# - 从异步方法更新进度条

标签 c# .net asynchronous async-await

我试图更好地理解如何从异步操作更新 Windows 窗体进度条,但我从中得到了一些意想不到的行为。

基本上我有一个按钮,点击后应该更新进度条,然后在进度条 100% 更新后将其设置回 0。

这是我的代码:

    private async void button1_Click(object sender, EventArgs e)
    {
        await CallMethodAsync().ContinueWith((prevTask) => 
        {
            prevTask.Wait();
            progressBar1.Invoke(new Action(() => { progressBar1.Value = 0; }));
        });
    }

    private static async Task ExecuteMethodAsync(IProgress<double> progress = null)
    {
        double percentComplete = 0;
        bool done = false;

        while (!done)
        {
            if (progress != null)
            {
                progress.Report(percentComplete);
            }

            percentComplete += 10;

            if(percentComplete == 100)
            {
                done = true;
            }
        }
    }

    private async Task CallMethodAsync()
    {
        var progress = new Progress<double>();
        progress.ProgressChanged += (sender, args) => { progressBar1.Increment(10); };
        await ExecuteMethodAsync(progress);
    }

有了这个实现,即使我在应该更新进度条值的操作上调用“Wait()”,进度条也根本不会更新。

如果我删除这部分代码:

progressBar1.Invoke(new Action(() => { progressBar1.Value = 0; }));

进度条会更新,但它一直保持这样,我想在它完全填满后将其设置回 0,这样我可以在再次单击按钮时再次更新它。

有人可以解释一下我做错了什么吗?

最佳答案

发明 async-await 语法的原因之一是,当使用 ContinueWith 等函数连接任务时,很难遵循指令序列。

如果您使用 async-await,则很少需要使用像 ContinueWith 这样的语句。在 await 之后,线程已经继续执行 await 之后的语句。

如果单击按钮,您要调用 ExcecuteMethodAsync。此函数采用 IProgress,因为它要定期报告进度。你想异步调用这个函数,所以每当函数必须等待某事时,它并没有真正等待,而是将控制权返回给你,这样你就可以做其他事情而不是真正等待,直到遇到等待,在这种情况下你的调用者继续处理,直到他遇到 await 等。

async-await 的好处是,在调用异步函数后继续执行的线程与调用线程具有相同的上下文。这意味着您可以将其视为您的原始线程。无需 InvokeRequired,无需使用互斥锁等保护数据。

您的函数可以简化如下:

async Task CallMethodAsync()
{
    var progress = new Progress<double>();
    progress.ProgressChanged += OnProgressReported;

    await ExecuteMethodAsync(progress);
}

private void OnProgressReported(object sender, ...)
{
    // because this thread has the context of the main thread no InvokeRequired!
    this.progressBar1.Increment(...);
}

private async void button1_Click(object sender, EventArgs e)
{
    await CallMethodAsync();
}

因此,当单击按钮时,将调用 CallMethodAsync。此函数将创建一个 Progress 对象并订阅其 Report 事件。请注意,这仍然是您的 UI 线程。然后它调用 ExecuteMethodAsync,这将定期引发事件 Report,该事件由 OnProgressReported 处理。

因为 ExecuteMethodAsync 是异步的,所以您可以确定其中有一个 await。这意味着无论何时必须等待,控制权都会返回给调用者,即 CallMethodAsync,直到遇到等待,在本例中是立即等待。

Control 在调用堆栈中上升到调用者,即 button1_click,它立即遇到 await,因此 Control 在调用堆栈中上升,等等。

所有这些控件都有相同的上下文:就好像它们是同一个线程。

一篇对我理解 async-await 帮助很大的文章是 this interview with Eric Lippert.在中间某处搜索异步等待

另一篇帮助我学习良好实践的文章是 this article by the ever so helpful Stephen ClearyAsync/Await - Best Practices in Asynchronous Programming同样由斯蒂芬克利里

关于c# - 从异步方法更新进度条,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44959668/

相关文章:

c# - 抽象可重用库中的合成根

c# - 如何在 .NET 中每小时(或每小时特定时间间隔)引发一个事件?

c# - 后台 worker 没有停止

c# - 如何正确隐藏基类属性

C#/.NET : FInd out whether a server exists, 为 SVC 记录查询 DNS

c# - 从数据库中提取大量数据时出现内存不足异常

javascript - 中断 Node.js 中的繁忙循环

python - 如何在tornado.wsgi.WSGIContainer中使用异步tornado API?

c# - Task.WaitAll 因异步/等待任务而挂起

c# - 将值从动态文本框复制到锯齿状数组错误