主线程上的Worker,其他线程上的UI.我做错了什么

本文关键字:线程 错了 什么 Worker 其他 UI | 更新日期: 2023-09-27 18:26:53

我正在为一个应用程序开发一个插件,该插件允许创建其他表单。有些插件是严格的"运行即忘"工作者,但有些插件有用于进一步控制的UI。该插件公开了一个API(通过.dll),该API应被子类化(通过其核心类)。它有一个初始化插件的方法,还有一个关闭插件的方法。

我想做的是在主线程上有工作线程,在另一个线程上有UI线程,我可以向它发送事件,也可以从(启动、停止等)接收控制事件

我当前的实现已经成功更新了UI;不幸的是,BeginInvoke没有用,因为表单是在我的主线程上初始化的,即使它被实例化并显示在子线程中。

当我调用form1.UpdateScanningProgress(i)时,为什么这会导致GUI线程被阻塞?看来是这样。在下面的示例中,InvokeRequired始终为false,即使我在另一个线程中运行表单也是如此。

这是"主"代码。请注意,插件信息已被剥离。我想要尽可能的基础:

namespace TestBed
{
    class TestBedManager : PluginManager
    {
        Form1 form1;
        void UIInitialized(object sender, EventArgs e)
        {
        }
        void BeginScan(object sender, EventArgs e)
        {
            for (int i = 0; i <= 100; ++i)
            {
                Thread.Sleep(150);
                form1.UpdateScanningProgess(i);
            }
            Thread.Sleep(1000);
        }
        // Parent calls when plugin starts
        public void PluginStart()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            form1 = new Form1();
            form1.Initialized += Initialized;
            form1.BeginScan += BeginScan;
            Thread thread = new Thread(() =>
            {
                Application.Run(form1);
            });
            thread.Start();
            // Here is where I'd start my real work instead of simply joining
            thread.Join();
        }
        // Parent calls when plugin needs to stop
        public void PluginStop()
        {
        }
    }
}

这是表单代码:

namespace TestBed
{
    public partial class Form1 : Form
    {
        public event EventHandler Initialized;
        public event EventHandler BeginScan;
        public Form1()
        {
            InitializeComponent();
        }
        delegate void UpdateScanningProgressDelegate(int progressPercentage);
        public void UpdateScanningProgress(int progressPercentage)
        {
            if (this.InvokeRequired)
            {
                UpdateScanningProgressDelegate updateScanningProgress = new UpdateScanningProgressDelegate(UpdateScanningProgress);
                updateScanningProgress.BeginInvoke(progressPercentage, null, null);
            }
            else
            {
                this.scanProgressBar.Value = progressPercentage;
            }
        }
        delegate void StopScanningDelegate();
        public void StopScanning()
        {
            if (this.InvokeRequired)
            {
                StopScanningDelegate stopScanning = new StopScanningDelegate(StopScanning);
                stopScanning.BeginInvoke(null, null);
            }
            else
            {
                this.scanProgressBar.Value = 0;
            }
        }
        private void Form1_Load(object sender, EventArgs e)
        {
            if (Initialized != null)
                Initialized(this, EventArgs.Empty);
        }
        private void scanButton_Click(object sender, EventArgs e)
        {
            if (BeginScan != null)
                BeginScan(this, EventArgs.Empty);
        }
    }
}

我觉得我可能错过了一个小细节,如果能多给我一双眼睛,我会很感激的。

主线程上的Worker,其他线程上的UI.我做错了什么

我怀疑"this.InvokeRequired"是否真的返回false。但在任何情况下,您都调用了错误的"BeginInvoke"方法。

您应该调用Control.Invoke或Control.BeginInvoke。但是,您调用的是编译器生成的Delegate.BeginVoke方法。

Control.Invoke/BeginInvoke方法导致在控件的拥有线程上调用给定的委托实例。Delegate.BeginInvoke方法只是将委托的调用排队到ThreadPool。

按照现在编写代码的方式,您将进入调用UpdateScanningProgress方法的无休止循环,并且在任何时候都无法真正更新进度UI。可能GUI线程根本没有被阻塞…它只是从来没有被要求做任何有趣的事情。

就我个人而言,我根本不使用"InvokeRequired"。当返回false时,使用Invoke甚至BeginInvoke是无害的,没有它代码会更简单。所以我总是直接调用Invoke/BeginInvoke。

试试这个:

public void UpdateScanningProgress(int progressPercentage)
{
    this.BeginInvoke((MethodInvoker)(() => this.scanProgressBar.Value = progressPercentage));
}