可取消的进度表和跨线程操作

问题描述 投票:0回答:1

我想创建一个带有进度条和取消按钮的表单。我的程序中的代码将运行算法并更新进度条。我实现了一个可以工作的原型(如下)。我想知道这是否是正确的实现,或者是否有更好的方法来做到这一点。特别是,我想知道跨线程访问 UI 进度条是否存在任何问题。我过去曾见过代码可以(似乎)正确运行,但在调试器下运行时会引发跨线程异常。

这是我实现可取消进度条的基本形式。

    public partial class CancellableProgressForm : Form
    {
        public CancellableProgressForm()
        {
            InitializeComponent();
        }

        public int PercentComplete
        {
            set
            {
                progressBar.Value = value;
            }
        }

        private void buttonCancel_Click(object sender, EventArgs e)
        {
            DialogResult = DialogResult.Cancel;
            this.Close();
        }
    }

以及用于在带有运行按钮的表单上测试它的原型代码:

        private void buttonRun_Click(object sender, EventArgs e)
        {
            var progressForm = new CancellableProgressForm();

            var progress = new Progress<int>(percent =>
            {
                progressForm.PercentComplete = percent;
            });

            Task.Run(() =>
            {
                DoWork(progress);
                progressForm.DialogResult = DialogResult.OK;
                progressForm.Close();
            });
            DialogResult dr = progressForm.ShowDialog();
            MessageBox.Show($"Result={dr}");
        }

        // Prototype code to "do some work" and update the progress bar on the progress form
        private void DoWork(IProgress<int> progress)
        {
            for (int i = 1; i <= 100; ++i)
            {
                Thread.Sleep(100);
                progress.Report(i);
            }
        }

正如我所说,无论有没有调试器,代码都可以正常运行。那么为什么没有抛出跨线程异常呢?还有更好的方法吗?

c# winforms user-interface thread-safety
1个回答
0
投票

规则是您只能从创建 UI 元素的线程中与该元素进行交互。 UI 组件是线程仿射的。它们不仅不是线程安全的,甚至不是自由线程的。因此,在

Task.Run
委托中所做的事情要非常小心。使用
Progress<T>
是可以的,因为该组件在创建时捕获同步上下文,并通过捕获的同步上下文传播所有报告,从而有效地执行上述线程亲和性规则。

您要做的就是将事件处理程序转换为

async void
await
Task.Run
。在
await
之后,您将返回 UI 线程。

private async void buttonRun_Click(object sender, EventArgs e)
{
    CancellableProgressForm progressForm = new();
    IProgress<int> progress = new Progress<int>(percent =>
    {
        progressForm.PercentComplete = percent;
    });

    await Task.Run(() => DoWork(progress));
    progressForm.DialogResult = DialogResult.OK;
    progressForm.Close();
    DialogResult dr = progressForm.ShowDialog();
    MessageBox.Show($"Result={dr}");
}

Progress<T>
类异步报告进度,这是高效的,因为它不会减慢后台工作,但它可能会产生一些奇怪的排序工件。如果您遇到问题,您可以尝试我在
here
发布的同步IProgress<T>实现。

© www.soinside.com 2019 - 2024. All rights reserved.