C#multithreading – 在线程之间移动对象

我正在使用一个winforms控件,它既是一个GUI元素,也是一些尚未向开发人员公开的内部处理。 当这个组件被实例化时,它可能需要5到15秒才能准备就绪,所以我想做的就是将它放在另一个线程上,当它完成时将它带回gui线程并将它放在我的表单上。 问题是这会(并且有)导致跨线程exception。

通常,当我使用工作线程时,它只使用简单的数据对象,我可以在处理完成后推回,然后使用已经在主线程上的控件,但我从来不需要以这种方式移动整个控件。

有谁知道这是否可能,如果可能,如何? 如果不是如何处理这样的问题,哪里有可能锁定主gui?

您不需要锁定GUI,只需要调用invoke:

Windows窗体中的控件绑定到特定线程,并且不是线程安全的。 因此,如果从另一个线程调用控件的方法,则必须使用控件的一个调用方法来编组对正确线程的调用。 此属性可用于确定是否必须调用invoke方法,如果您不知道哪个线程拥有控件,这可能很有用。 REF

以下是代码中的外观:

public delegate void ComponentReadyDelegate(YourComponent component); public void LoadComponent(YourComponent component) { if (this.InvokeRequired) { ComponentReadyDelegate e = new ComponentReadyDelegate(LoadComponent); this.BeginInvoke(e, new object[]{component}); } else { // The component is used by a UI control component.DoSomething(); component.GetSomething(); } } // From the other thread just initialize the component // and call the LoadComponent method on the GUI. component.Initialize(); // 5-15 seconds yourForm.LoadComponent(component); 

通常从另一个线程调用LoadComponent将导致跨线程exception,但是使用上面的实现,将在GUI线程上调用该方法。

InvokeRequired告诉您是否:

调用者必须在对控件进行方法调用时调用invoke方法,因为调用者与创建控件的线程不同。 REF

更新:
因此,如果我理解正确,控制对象是在GUI线程以外的线程上创建的,因此即使您能够将其传递给GUI线程,您仍然无法在不引起跨线程exception的情况下使用它。 解决方案是在GUI线程上创建对象,但在单独的线程上初始化它:

 public partial class MyForm : Form { public delegate void ComponentReadyDelegate(YourComponent component); private YourComponent _component; public MyForm() { InitializeComponent(); // The componet is created on the same thread as the GUI _component = new YourComponent(); ThreadPool.QueueUserWorkItem(o => { // The initialization takes 5-10 seconds // so just initialize the component in separate thread _component.Initialize(); LoadComponent(_component); }); } public void LoadComponent(YourComponent component) { if (this.InvokeRequired) { ComponentReadyDelegate e = new ComponentReadyDelegate(LoadComponent); this.BeginInvoke(e, new object[]{component}); } else { // The component is used by a UI control component.DoSomething(); component.GetSomething(); } } } 

不太了解对象。 为了避免跨线程exception,您可以使初始线程调用一个调用(即使您是从线程调用)。

从我自己的应用程序中复制并粘贴:

  private delegate void UpdateStatusBoxDel(string status); private void UpdateStatusBox(string status) { listBoxStats.Items.Add(status); listBoxStats.SelectedIndex = listBoxStats.Items.Count - 1; labelSuccessful.Text = SuccessfulSubmits.ToString(); labelFailed.Text = FailedSubmits.ToString(); } private void UpdateStatusBoxAsync(string status) { if(!areWeStopping) this.BeginInvoke(new UpdateStatusBoxDel(UpdateStatusBox), status); } 

基本上,线程任务将调用“异步”方法。 然后将告诉主要表单开始调用(实际上是异步本身)。

我相信可能有一个更短的方法来完成所有这些,而无需创建委托和两种不同的方法。 但这种方式刚刚根深蒂固。 这就是微软书籍教给你的东西:p

BackgroundWorker类就是针对这种情况而设计的。 它将为您管理线程,让您启动线程,以及取消线程。 线程可以将事件发送回GUI线程以进行状态更新或完成。 这些状态和完成事件的事件处理程序位于主GUI线程中,可以更新WinForm控件。 并且WinForm没有被锁定。 这就是你需要的一切。 (并且在WPF和Silverlight中也同样有效。)

必须从UI线程创建和修改控件,没有办法解决这个问题。

为了在执行长时间运行初始化时保持UI响应,请将进程保留在后台线程上并调用任何控制访问权限。 UI应保持响应,但如果不响应,则可以向后台线程添加一些等待时间。 这是一个例子,使用.Net 4并行工具: http : //www.lovethedot.net/2009/01/parallel-programming-in-net-40-and_30.html

如果在初始化完成之前不允许与初始化的特定控件进行交互,则隐藏或禁用它直到完成。