主线程上的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);
}
}
}
我觉得我可能错过了一个小细节,如果能多给我一双眼睛,我会很感激的。
我怀疑"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));
}