2011-11-25 53 views
4

在我的应用程序中,我启动了一个隐藏的dummyForm,它仅仅是为了跟踪主UI线程而创建的。所以如果将要创建一个新窗体InvokeRequired在虚拟窗体上使用,以确保我们在创建新窗体时位于主UI线程上。线程与WinForms?

直接在实例化我的frmStart表单后,我检查了frmStart.InvokeRequired,它被设置为false,因此不需要在这里调用(dummyForm.InvokeRequired也是如此)。

后来我有一个frmMyDialog将使用frmStart父/所有者是这样的:

using(Create frmMyDialog on main UI thread) 
{ 
    frmMyDialog.Show(frmStart); 
} 

,这将抛出一个跨线程异常,这里的奇怪的是:

frmMyDialog.InvokeRequired = false 
dummyForm.InvokeRequired = false 
frmStart.InvokeRequired = true 

甚至当我在创建frmStart时检查dummyForm.InvokeRequired为假?

frmMyDialog.InvokeRequired应该始终与dummyForm.InvokeRequired的值相同?这里发生了什么?

我检查了frmStartdummyForm完全没有在第一个实例创建后重新创建。

EDIT1:

这是应用程序如何启动:

public static void Main(string[] args) 
{ 
    _instance = new MyClientMain(parameters); 
    Application.Run(_instance); 
} 

MyClientMain类的构造函数将在一个名为MainControl静态类运行安装程序。 MainControler将在设置方法实例化一个像这样的dummyform:

if (_dummyForm == null) 
    _dummyForm = new Form(); 

此步骤完成后登录表单将处理登录和这种形式是多线程的。当登录完成时,MainController将再次被调用以instanciate并打开包含frmStart的主MDI窗口。为了确保我们在同一线程上完成以下工作:

public static StartApplication() 
{ 
if (_dummyForm.InvokeRequired) 
        _dummyForm.Invoke(new MethodInvoker(delegate { OpenMainOrbitWindow(); })); 
    //Instanciate mainform and frmStart then open mainForm with frmStart as a MDI child 

} 

这里没有多线程。

然后,当服务下线时,一个事件将被触发,我需要弹出一个frmMyDialog,但使用时。的ShowDialog(),它的对话框将被放在后面表格,以便家长/所有者最多可以发现这样设置:

public static Form GetActiveForm() 
     { 
      Form activeForm = Form.ActiveForm; 

      if (activeForm != null) 
       return activeForm; 

      if (MainOrbitForm.TopMost) 
       return MainOrbitForm; 
      else 
      { 
       FormCollection openForms = Application.OpenForms; 
       for (int i = 0; i < openForms.Count && activeForm == null; ++i) 
       { 
        Form openForm = openForms[i]; 
        if (openForm.IsMdiContainer) 
         return openForm.ActiveMdiChild; 
       } 
      } 

      if (_patientForm != null) 
      { 
       if (_patientForm.TopMost) 
        return _patientForm; 
      } 

      return null; 
     } 

     public static string ShowOrbitDialogReName() 
     { 
      frmMyDialog myDialog; 
      Form testForm; 

      //Makes sures that the frmOrbitDialog is created with the same thread as the dummyForm 
      //InvokeRequired is used for this 
      using (myDialog = MainController.CreateForm<frmOrbitDialog>()) 
      { 
       //Settings... 
       testForm = GetActiveForm(); 
       myDialog.ShowDialog(GetActiveForm(testForm)); 


      } 
     } 

的问题是,

myDialog.InvokeRequired = false 
testForm.InvokeRequired = true; 
MainController.DummyForm.InvokeRequired = false; 

EDIT2: 启动和创建dummyform:

dummyForm.InvokeRequired = false 
Thread.CurrentThread.ManagedThreadId = 9 

登录成功后,我们创建的MainForm

_mainForm.InvokeRequired = false 
MainControl.DummyForm.InvokeRequired = false 
Thread.CurrentThread.ManagedThreadId = 9 

目前为止一切都很好。然后回调接收(WCF)和事件创建相同的线程上frmMyDialog(调用是在dummyForm使用),然后使用ShowDialog的:

frmMyCustomDialog.ShowDialog(_mainForm) 

这将引发CrossThreadException,这是它的外观像这样:

_mainForm.InvokeRequired = true 
frmMyCustomDialog.InvokeRequired = false 
MainControl.DummyForm.InvokeRequired = false 
Thread.CurrentThread.ManagedThreadId = 12 

为什么MainControl.DummyForm不正确? ManageThreadId不是9但是12?

+0

显示更多*真实*代码,了解如何创建涉及您获得的例外的表单。 InvokeRequired未绑定到主UI线程,而是绑定到创建控件/窗体的线程! – Jan

+0

你是否多次调用Application.Run? – Jodrell

+0

@Jodrell请参阅Edit1 – Banshee

回答

2

您应该使用System.Threading.SynchronizationContext.Current。它是直接为此目的而创建的。 您的应用第一种形式创建完成后,您可以在任何地方访问它。从你的例子来看,这应该不成问题,因为你在应用程序的开始时创建了一个表单。

public static void Main(string[] args) 
{ 
    _instance = new MyClientMain(parameters); 
    Application.Run(_instance); 
} 

然后在任何地方,你需要的代码要在UI线程上执行,您只需使用

System.Threading.SynchronizationContext.Current.Send() // To execute your code synchronously 
System.Threading.SynchronizationContext.Current.Post() // To execute your code synchronously 

记住,的SynchronizationContext是smartenough看,你从UI线程已经调用它,那么它将直接执行您的委托。 此外,请记住,在首次使用SynchronizationContext之前,您需要创建一些WinForms窗体或控件,因为当您这样做时,上下文将被初始化为适当的实现。

有3个实现:默认,什么都不做 - 只是总是运行代码同步,它保持在当前,直到你创建WinForms控件或WPF控件。 然后当前将填充Winforms的上下文或WPF调度程序的上下文。

+0

上看看谢谢,但即使在创建并打开表单后,System.Threading.SynchronizationContext.Current也为null? – Banshee

+0

嗯,显然SynchronizationContext是每个线程的singelton,当frmMyDialog即将显示时,好像我们在另一个线程上。 – Banshee

+0

你必须让你的窗体可见。 –

0

我没有完全理解你的问题,因为你的例子并没有真正显示任何有关多线程的内容 - 但是,如果你想创建一个窗体,其中父窗体是另一个窗体的另一个窗体,你可以使用下面的代码:

public void CreateShowDialogForm() 
{ 
    if (this.InvokeRequired) 
    { 
    this.Invoke(new Action(CreateShowDialogForm)); 
    } 
    else 
    { 
    Form frmMyDialog = new Form(); 
    frmMyDialog.Show(this); 
    } 
} 

private void Form4_Load(object sender, EventArgs e) 
{ 
    Task t = new Task(() => CreateShowDialogForm()); 
    t.Start(); 
    t.ContinueWith(task => true); 
} 
+0

嗯,不知道这是对的,请在Edit1 – Banshee

1

这只是我的头顶,但正如Vladimir Perevalov在其他讨论中指出的那样,您必须使您的表单可见。

如果您的frmDummy从不显示,那么它永远不会创建并分配窗口句柄,因此它总是会将False回复为“InvokeRequired”。这将意味着您通过frmDummy同步的所有代码都不会实际发送到初始UI线程,但始终在当前线程中运行。 (它成为它刚刚创建的控件的自己的UI线程)。

重要的是要注意的是,InvokeRequired试图确定该控件的窗口句柄是否由另一个线程拥有。它与构造函数无关。

如果你不想显示frmDummy,你可以在实例化它之后调用CreateControl,以确保它具有分配的句柄。