【问题标题】:UI gets blocked while the progress bar gets updated进度条更新时 UI 被阻止
【发布时间】:2017-07-18 02:14:50
【问题描述】:

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

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

首先,async/await 用法可以像 BackgroundWorker 那样填充进度条吗?这两种方法有什么共同点吗?

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

有人可以解释一下我做错了什么吗?我该如何修改下面的代码以在进度条更新时保持 UI 响应? 在更新进度条时,async/await 用法是否可以像 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);
}

【问题讨论】:

  • 不要等待更新栏的任务,使用this.Invoke(new Action()....更新UI。
  • 忽略@Crowcoder 的评论。你的代码很好。等待和使用Progress&lt;T&gt; 实际上是这种情况的现代成语。您的代码中的问题是您没有做任何 实际 工作。你只是用更新向 UI 线程发送垃圾邮件,它跟不上。例如,如果您将迭代计数更改为100 而不是1000000,并在while 循环中添加await Task.Delay(500);,您不仅可以摆脱await Task.Run()(即循环可以在您的 ExecuteMethodAsync() 方法中而不是 lambda 中),它将按您的意愿工作。

标签: c# .net asynchronous


【解决方案1】:

您的代码基本上没问题。唯一的问题是它不做任何实际的工作,所以它把所有的时间都花在了尝试更新 UI 上。在我的电脑上,整个事情在几秒钟内完成,但由于 UI 忙于处理进度更新,它几乎没有时间留给其他用户交互。例如,如果我拖动窗口,则在任务循环执行期间,窗口位置只会更新一次或两次。

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

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

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() 方法,其余代码很多 更简单。因为我使用的是await,所以我可以直接使用progressBar1对象,而不是使用像Progress&lt;T&gt;这样的跨线程调用机制

请注意,在上面,我只等待 10 毫秒。这大约是 Windows 线程调度程序的限制——它不能比这更精确地调度线程——但这仍然足够慢,可以让 UI 线程跟上更新。现实世界的场景可能需要比这更长的工作块,从 100 毫秒开始。

即使更新频率相对较高,像拖动窗口这样的 UI 交互也能顺利进行。

【讨论】:

    猜你喜欢
    • 2011-12-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-26
    • 1970-01-01
    • 2018-01-16
    • 2020-02-21
    相关资源
    最近更新 更多