从异步方法更新进度条

时间:2017-07-06 22:10:34

标签: 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,这样当我再次单击按钮时我可以再次更新它。

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

2 个答案:

答案 0 :(得分:4)

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

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

如果单击该按钮,则要调用ExcecuteMethodAsync。此功能需要IProgress,因为它希望定期报告进度。你想异步调用这个函数,所以每当函数必须等待某个东西时,它都不会等待,但是会将控制返回给你,这样你就可以做其他事情而不是真正等待,直到你遇到await,in在哪种情况下,你的来电者会继续处理,直到他遇到等待等等。

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。此函数将创建A Progress对象并订阅其Report事件。请注意,这仍然是您的UI线程。然后它调用ExecuteMethodAsync,它将定期引发事件Report,由OnProgressReported处理。

因为ExecuteMethodAsync是异步的,所以你可以确定它中存在等待的地方。这意味着无论何时必须等待,控制权都会返回到调用者CallMethodAsync,直到遇到await,在这种情况下会立即进行。

控制将调用堆栈调到调用者,这是button1_click,它会立即遇到await,因此控制会上升到调用堆栈等。

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

一篇帮助我理解async-await的文章是this interview with Eric Lippert.在中间寻找异步等待

另一个帮助我学习良好实践的知识是this article by the ever so helpful Stephen ClearyAsync/Await - Best Practices in Asynchronous Programming也是Stephen Cleary

答案 1 :(得分:-1)

您的问题正在发生,因为 ExecuteMethodAsync(...)实际上不是异步的。
在while循环之前添加以下内容以使其异步

await Task.Delay(1);

或将代码的某些同步部分(例如while循环)封装到a中:

await Task.Run(() => { ... });

或(最好的),在函数的开头添加以下内容:

await Task.Yield(); // Make us async right away