【问题标题】:Ensuring that child controls are created in main UI thread确保在主 UI 线程中创建子控件
【发布时间】:2011-11-30 18:41:44
【问题描述】:

我正在修改现有的 WinForms 项目。该项目具有用户控件。 此 UserControl 具有 DataSet 变量,该变量是从程序的另一部分在不同线程中设置的。

我想要做的是根据DataSet动态添加另一个控件到这个控件。

因此,在加载 DataSet 后,我​​正在调用 RefreshChildControl 函数并尝试将我的新 ChildUserControls 添加到 flowLayoutPanel。这就是问题开始的地方:)。我得到“跨线程操作无效:控件'ChildUserControl'从创建它的线程以外的线程访问”异常。我尝试使用 if(this.InvokeRequired) 并调用此方法,但没有帮助。 MyUserControl 上的 InvokeRequired 为 false。

那么,有没有什么好的方法来完成这样的任务呢?还是我错过了什么重要的东西?

编辑:

我试图跳过 InvokeRequired 测试并在此方法上调用 this.FindForm().Invoke。我有“在创建窗口句柄之前,不能在控件上调用 Invoke 或 BeginInvoke。”例外。而且,顺便说一句,当我用这个控件打开另一个表单时,一切正常。

【问题讨论】:

  • 请注意 InvokeRequired 有问题:ikriv.com/en/prog/info/dotnet/MysteriousHang.html
  • 不要使用 UserControl 来执行这个测试,它正忙于创建。而是使用您要将其添加到的表单。或者完全跳过测试,你知道这段代码在错误的线程上运行。使用 BackgroundWorker 来帮助您做到这一点。
  • 我试图跳过 InvokeRequired 测试并在此方法上调用 this.FindForm().Invoke。我有“在创建窗口句柄之前,不能在控件上调用 Invoke 或 BeginInvoke。”例外。而且,顺便说一句,当我用这个控件打开另一个表单时,一切正常。因此,看起来表单句柄在调用时并未创建。但不幸的是,我不知道该怎么办。在这种情况下如何使用 BackgroundWorker?

标签: c# .net winforms multithreading


【解决方案1】:

首先。最简单的解决方案是每次都执行 Invoke。不会有什么不好的事情发生。 其次,使用 SynchronizationContext。

using System.Threading;
public class YourForm
{
    SynchronizationContext sync;
    public YourForm()
    {
        sync = SynchronizationContext.Current;
        // Any time you need to update controls, call it like this:
        sync.Send(UpdateControls);
    }

    public void UpdateControls()
    {
        // Access your controls.
    }
}

SynchronizationContext 将为您管理所有线程问题。它会检查您是从同一个线程还是从另一个线程调用。如果来自同一个,它将立即执行您的代码。否则它将通过表单的消息循环调用。

【讨论】:

  • 如果从控件调用的 SynchronizationContext.Current 为空怎么办?是不是表示表单没有初始化?
  • 是的。在任何控件完全构建并显示后,它将被初始化。所以你的表单是第一个显示的,你可能想把我的代码移到 Form_Load
  • 对不起,我可能不清楚。现在我从分配控件数据集的函数中调用 Send 方法。此方法在外部调用。但是如果我将代码输出到控件的 Load 事件处理程序中,我猜 dataSet 可能不会被加载。
  • 嗯。这很奇怪。如果在创建任何控件之前调用 SynchronizationContext.Current,它将为空。但是从您的故事看来,您应该已经有一个带有 UserControl 的表单,但您仍然无法获得 SynchronizationContext 实例?关于 Load 处理程序,如果您立即显示带有控件的表单,则该 Load 也将在此之后立即被调用。在我看来,这应该不是问题。另外,再次考虑一下,每次都简单地调用 Invoke,这是一种直接而好的方法。
  • 是的,您对 Load 处理程序中的 SynchronizationContext 是正确的。我想我昨天晚上只是在代码中弄乱了一些东西:)。非常感谢您快速明确的回答。
【解决方案2】:

如果您的用户控件在构建后没有立即可见,则不会在您认为创建它的线程上创建句柄。不是 C# 对象的线程父对象很重要,而是 Windows Handle 对象的父对象很重要。

要强制在您认为创建控件的线程上立即创建控件,然后 读出control.Handle,这将强制控件实际生成并分配一个句柄。

 MyUserControl uc = new MyUserControl(); // the handle is not created here
 uc.Visible = false;
 IntPtr dummy = uc.Handle; //  The control is immediately given a real handle

您也可以尝试使用 uc.CreateControl,但如果控件不可见,则不会创建句柄。

现在即使用户控件不可见,您也可以让另一个线程更新您的用户控件。

 uc.BeginInvoke((Action)(() => uc.Text = "ha ha"));

如果您省略 dummy = uc.Handle 行,您将获得一个异常,即您无法在没有句柄的控件上调用 BeginInvoke。

http://msdn.microsoft.com/en-us/library/system.windows.forms.control.createcontrol(v=vs.90).aspx

【讨论】:

    猜你喜欢
    • 2010-11-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多