我正在使用一个winforms控件,它既是一个GUI元素,也是一些尚未向开发人员公开的内部处理。当这个组件被实例化时,它可能需要5到15秒才能准备就绪,所以我想做的就是将它放在另一个线程上,当它完成时将它带回gui线程并将其放在我的表单上。问题是这会(并且有)导致跨线程异常。
通常当我使用工作线程时它只使用简单的数据对象,我可以在处理完成时推回,然后使用已经在主线程上的控件,但我从来不需要以这种方式移动整个控件。
有谁知道这是否可能,如果可能,如何?如果不是如何解决这样一个有可能锁定主gui的问题?
答案 0 :(得分:3)
您无需锁定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
会导致跨线程异常,但是通过上面的实现,该方法将在GUI线程上调用。
InvokeRequired
告诉您是否:
调用者必须调用invoke方法 在进行方法调用时 控制因为呼叫者在 不同的线程比一个 控制创建于。 ref
<强>更新强>
因此,如果我理解正确,控制对象是在GUI线程以外的线程上创建的,因此即使您能够将其传递给GUI线程,您仍然无法在不引起跨线程异常的情况下使用它。解决方案是在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();
}
}
}
答案 1 :(得分:1)
不了解对象太多。为了避免跨线程异常,您可以使初始线程调用一个调用(即使您是从线程调用)。
从我自己的应用程序中复制并粘贴:
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);
}
基本上,线程任务将调用“Async”方法。然后将告诉主要表单开始调用(实际上是异步本身)。
我相信可能有更短的方法来完成所有这些,而无需创建委托和两种不同的方法。但这种方式刚刚根深蒂固。这就是微软书籍教给你的东西:p
答案 2 :(得分:0)
BackgroundWorker类专门针对这种情况而设计。它将为您管理线程,让您启动线程,以及取消线程。线程可以将事件发送回GUI线程以进行状态更新或完成。这些状态和完成事件的事件处理程序位于主GUI线程中,可以更新WinForm控件。并且WinForm没有被锁定。这就是你需要的一切。 (并且在WPF和Silverlight中也同样有效。)
答案 3 :(得分:0)
必须从UI线程创建和修改控件,没有办法解决这个问题。
为了在执行长时间运行的初始化时保持UI响应,请将进程保留在后台线程上并调用任何控制访问权限。 UI应保持响应,但如果不响应,则可以向后台线程添加一些等待时间。这是一个使用.Net 4并行工具的示例:http://www.lovethedot.net/2009/01/parallel-programming-in-net-40-and_30.html
如果在初始化完成之前不允许与初始化的特定控件进行交互,则隐藏或禁用它直到完成。