有没有一种方法可以找到控件所属的线程?

8

我正在处理一个大型应用程序的线程问题(出现了跨线程异常)。有没有办法找到特定控件创建的线程名称/ID?

当我尝试将新控件添加到我的控件集合时,就会出现错误。我无法创建一个小的、可重现的示例,因此我会尽可能描述它。

我有一个主控件,它位于一个窗体上,称之为_mainControl。在它的构造函数中,我实例化另一个控件的实例,类似于:

ChildControl _childControl = new ChildControl();

现在_childControl已经存在,但我还没有将它添加到_mainControls集合中。
最终,_mainControl会收到一个事件通知,告诉我应该添加这个控件。在事件处理程序中,我会检查是否需要使用InvokeRequired,如果需要,就会调用处理程序,类似下面这样:
AddControlEventHander(...)
{
    if(InvokeRequired)
    {
        BeginInvoke(new MethodInvoker(AddControlEventHander);
        return;
    }
    Controls.Add(_childControl);
}

异常总是在Controls.Add中抛出(“跨线程操作无效:在非创建它的线程上访问控件'_item'”)。

现在,我不明白的是这是如何可能的。我在与_mainControl相同的线程上创建了_childControl。在调试时查看线程窗口时,当我调用Control.Add时,当前线程名称/ID与添加_childControl时相同。然而,最让我困惑的是_mainControl的以下调用:

InvokeReuqired == false;
_childControl.InvokeRequired == false;
_childControl._item.InvokeRequired == true; //I made _item public just to try this and it returns true!

这怎么可能?_childControl可能在一个线程上创建,而它的子元素却在另一个线程上创建吗?所有_childControl的子元素都是在初始化期间创建的,就像通常做的那样。
如果有人有任何提示/建议,请告诉我。
谢谢。
编辑:
如果有人感兴趣,我找到了发生了什么。 我很好奇控件如何可以在一个线程上创建,而它的子元素却在另一个线程上创建,即使InitializeComponent都在同一个线程上完成。 所以,我使用了类似于Charles下面建议的代码来找出创建子元素的线程。 一旦我知道了那个,我至少知道了要关注哪个线程。 然后我在子控件的OnHandleCreated事件上断点,找到了问题所在。
我不知道的一件事是,控件的句柄是在控件第一次变得可见时创建的,而不是在创建时创建的。 因此,尝试将其可见性设置为true的线程并没有拥有该控件。 所以我添加了一个检查来查看是否InvokeRequired,并认为这将起作用。 然而,我真正没想到的是,如果控件的句柄还没有被创建,调用InvokeRequired将创建控件的句柄! 这实际上会导致控件在错误的线程上创建,并始终为InvokeRequired返回false。 我通过触摸控件的Handle属性来解决这个问题,以便在调用InvokeRequired之前创建它。
谢谢大家的帮助 :)

谢谢提示。我也发现无害的 if (control.Handle != null) ... 实际上在该线程上创建了控件! - Mark Lakata
请查看我的答案,链接在这里:http://stackoverflow.com/questions/8331144/ensuring-that-child-controls-are-created-in-main-ui-thread/17054689#17054689 - Mark Lakata
1个回答

5
获取控件的所有者线程,请尝试以下操作:
private Thread GetControlOwnerThread(Control ctrl)
{
    if (ctrl.InvokeRequired)
        ctrl.BeginInvoke(
            new Action<Control>(GetControlOwnerThread),
            new object[] {ctrl});
    else
        return System.Threading.Thread.CurrentThread;
}

子控件是否可以在与父容器控件不同的线程上运行?是的,这取决于构造控件(new'ed up)时运行的线程。

您始终需要检查InvokeRequired...因为您永远不知道可能调用编码方法的线程是哪个...是否需要单独检查每个子控件的InvokeRequired取决于您对所有控件是否在同一线程上创建有多么确定。如果所有控件都是在表单创建时,在同一初始化程序中创建的,则您可能安全地假定它们都在同一线程上创建。


嗯,那么在尝试查看是否需要对控件使用InvokeRequired时,需要进行哪些检查呢?您是否总是需要在控件及其所有子控件上检查InvokeRequired? - Flack
是的,你总是需要检查InvokeRequired...因为你永远不知道哪个线程可能会调用你正在编写的方法...是否需要为每个子控件单独检查InvokeRequired,取决于您对所有控件是否在同一线程上创建有多么确定。如果所有控件都是在窗体创建时,在同一个初始化程序中创建的,则您可以安全地假设它们都是在同一个线程上创建的。 - Charles Bretana
这对我来说看起来很奇怪。据我所知,_childControl及其所有子项似乎都是在同一个线程上创建的。此外,如果_childControl的子项以某种方式在不同的线程上创建,我不明白如何调用_mainControl的Controls.Add,因为由于跨线程而创建_childControl句柄时会失败。无论是由于_childControl还是由于其子项,我都会失败。我不明白这是如何可能的。我将重新审视它并进行一些重构。也许它会自然消失 :) - Flack
如果在线程A上创建了一个容器控件,并且在另一个线程(比如线程B)上创建了其他控件,那么您应该能够将该控件添加到在线程A上创建的容器的Controls集合中。您正在修改容器控件,而不是子控件。 - Charles Bretana
6
“Action<Control>” 应该改为 “Func<Control, Thread>”,因为 "Action" 没有返回值。 "BeginInvoke" 应该改为 "Invoke",因为 "BeginInvoke" 没有返回值。 最后一句话应该改为 "应该以 'return (Thread)ctrl.Invoke...' 开始”。 - Gerard

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接