使用TPL任务更新UI不会立即更新UI

3

我有一个.NET Windows表单,它创建了一个异步运行的任务。该任务应该调用以更新其进度的UI。它可以工作,但是进度条只会延迟更新。

public partial class WaitDialog : Form
{
    private readonly TaskScheduler _uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();

    private void ReportViewerWaitForm_Load(object sender, EventArgs e)
    {
        _asyncTask = Task.Factory.StartNew(Calculate(), _cancellationTokenSource.Token);   
        _asyncTask.ContinueWith(task => Close(), CancellationToken.None, TaskContinuationOptions.None, _uiScheduler);
    }

    private void Calculate()
    {
        UpdateProgressCount(0, 1000);

        for (int i = 0; i < 1000; i++)
        {
            // do some heavy work here
            UpdateProgressCount(i);
        }
    }

    private void UpdateUserInterface(Action action)
    {
        Task.Factory.StartNew(action, CancellationToken.None, TaskCreationOptions.None, _uiScheduler).Wait();
    }

    public void UpdateProgressCount(int count)
    {
        UpdateUserInterface(() => progressBar.Value = count);
    }

    public void UpdateProgressCount(int count, int total)
    {
        UpdateUserInterface(() =>
            {
                progressBar.Minimum = 0;
                progressBar.Maximum = total;
            });
        UpdateProgressCount(count);
    }

    private void WaitForm_FormClosing(object sender, FormClosingEventArgs e)
    {
        if (!_asyncTask.IsCompleted)
        {
            e.Cancel = true;
        }
    }
}

进度条被正确设置,当表单关闭时,其值被设为1000(或100%),但在UI上并未以此方式显示,它仅显示大约50%的完成情况。
更新UI的任务已启动,然后调用了Wait()函数,但异步任务似乎在更新UI之前继续运行。我猜这是因为UI线程本身进行了某种BeginInvoke()来更新UI。
最终,当异步(重型工作)任务完成时,UI没有完全更新,表单关闭了。但是UI任务中的Wait()、Application.DoEvents()或progressBar.Update()都没有任何效果,不能允许在返回到重型工作任务之前更新UI。
2个回答

1
在UpdateProgressCount中,您可能希望使用进度来调用表单,而不是创建另一个任务。这是更新事物的标准方式,而不是通过创建另一个任务。
此外,我认为等待在UI线程上运行的任务时,您的后台线程将继续运行。但是我可能对这部分不正确。无论如何,如果您使用进度调用表单,那应该可以解决您的问题。

所以,用Invoke(action)替换UpdateUserInterface()中的任务是不起作用的。 - robertkroll
你确认进度条的最大值设置为1000了吗? - user981225
是的,在FormClosing事件触发时,它肯定已经达到了1000。UI只是在UI任务完成后没有立即更新。如果我在UI任务Wait()之后添加Thread.Sleep(50),进度条就会达到75%。如果我将其更改为Thread.Sleep(100),进度条将达到95%。此外,如果我从异步任务继续中删除Close()调用,自然地UI完成更新并且进度条达到100%。 - robertkroll
任何地方都不应该有Wait()。如果你用invoke替换updateuserinterface,它应该可以工作。 - user981225
@user981225 调用应该可以工作,但 OP 的解决方案也应该可以。我很好奇这个 bug 是什么。 - usr
它不起作用。我认为这是因为在Form_Load方法中的异步任务继续之前,表单关闭了所有UI消息的泵送。如果progressBar被设置为50,似乎没有办法等待UI实际上在进度条中显示该值,然后继续进行异步任务。 - robertkroll

0
不要为UI更新创建另一个任务,在WinForm progressBar 中调用invoke方法来更新状态。
</p>替换原文中的


UpdateUserInterface(() => progressBar.Value = count)

if (progressBar.InvokeRequired) {
    progressBar.Invoke(() => progressBar.Value = count);
} else {
    progressBar.Value = count;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接