C#多线程 - 在线程之间移动对象

时间:2010-06-01 23:36:03

标签: c# winforms multithreading

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

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

有谁知道这是否可能,如果可能,如何?如果不是如何解决这样一个有可能锁定主gui的问题?

4 个答案:

答案 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

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