在进度条更新时,UI被阻止

时间:2017-07-17 12:39:10

标签: c# .net asynchronous

我正在尝试使用async / await调用运行操作时更新进度条,但在更新进度条时UI会冻结。

基本上,当使用BackgroundWorker时,这是一个非常简单的要求,但现在使用async / await调用似乎有点复杂。

首先,异步/等待使用可以像BackgroundWorker一样填充进度条吗?这两种方法之间有什么共同之处吗?

在我的用户界面上,我有一个进度条和一个按钮,当点击按钮时,进度条开始更新,但UI冻结,这不应该发生,因为async / await应该以“并发”的方式工作。   如果我使用BackgroundWorked执行此操作,则UI不会冻结。

有人可以解释一下我做错了什么,为了在进度条更新时保持UI响应,我怎么能修改下面的代码?  在更新进度条时,异步/等待使用的行为可以像BackgroundWorker那样吗?

以下是我的代码:

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

private async Task CallMethodAsync()
{
    this.progressBar1.Value = 0;
    this.progressBar1.Maximum = 1000000;
    var progressbar1 = new Progress<double>();
    progressbar1.ProgressChanged += Progressbar1_ProgressChanged;

    await ExecuteMethodAsync(progressbar1);
}

private async Task ExecuteMethodAsync(IProgress<double> progress = null)
{
    await Task.Run(new Action(() => { 

        double percentComplete = 0;
        bool done = false;

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

            percentComplete += 1;

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

private void Progressbar1_ProgressChanged(object sender, double e)
{
    this.progressBar1.Increment(1);
}

1 个答案:

答案 0 :(得分:1)

你的代码基本上没问题。唯一的问题是它没有做任何实际的工作,所以它花费所有的时间来尝试更新UI。在我的计算机上,整个过程在几秒钟内完成,但由于UI忙于处理来自进度的更新,因此几乎没有时间用于其他用户交互。例如,如果我拖动窗口,窗口位置将在任务循环执行期间仅更新一次或两次。

在实际情况中,您可以在主要工作块之间更频繁地更新进度。您可以通过摆脱循环外的Task.Run()并在循环内使用Task.Delay()代表您长期运行的工作,在您已获得的代码中更好地模拟这一点。在实际情况中,您可以将Task.Delay()替换为Task.Run()来执行个人工作组件。

当然,如果你这样做,你不再需要Progress<T>课程。它确实是一个有用的类,但通常在使用async / await时,工作和进度更新可以交替使用,允许您返回UI线程以使用进度更新await而不是必须通过Progress<T>。如果您按照我的建议更改代码示例,则看起来更像是:

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

private async Task CallMethodAsync()
{
    this.progressBar1.Value = 0;
    this.progressBar1.Maximum = 1000;

    await ExecuteMethodAsync();
}

private async Task ExecuteMethodAsync()
{
    for (int percentComplete = 0; percentComplete < 1000; percentComplete++)
    {
        await Task.Delay(10);
        progressBar1.Increment(1);
    }
}

甚至不需要Progressbar1_ProgressChanged()方法,其余代码的 lot 更简单。因为我使用await,所以我可以直接使用progressBar1对象,而不是使用像Progress<T>这样的跨线程调用机制

请注意,在上面,我只等了10毫秒。这是关于Windows线程调度程序的限制 - 它不能比这更精确地调度线程 - 但是仍然足够慢以使UI线程跟上更新。一个真实世界的场景可能比这更长,大约100毫秒。

即使有相对较高的更新频率,也可以顺利地进行UI交互,例如拖动窗口。