使用WinForms进行线程化?

时间:2011-11-25 10:39:27

标签: c# winforms multithreading

在我的应用程序中,我启动了一个隐藏的dummyForm,它只是为了跟踪主UI线程而创建的。因此,如果要创建新表单,则在虚拟表单上使用InvokeRequired以确保在创建新表单时我们位于主UI线程上。

直接在实例化我的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

即使我在创建dummyForm.InvokeRequired时检查frmStart是否为假,这是偶然的?

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

我已经检查过在创建第一个实例后根本没有重新创建frmStartdummyForm

编辑1:

这就是应用程序的启动方式:

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

MyClientMain类的构造函数将在名为MainControl的静态类上运行安装程序。 MainControler将在setup方法中实现如下虚拟形式:

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

完成此操作后,登录表单将处理登录,此表单为多线程。登录完成后,将再次调用MainController以实例化并打开包含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.InvokeRequired = false
Thread.CurrentThread.ManagedThreadId = 9 

成功登录后,我们创建了mainform

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

到目前为止一切看起来都很好。然后收到一个回调(WCF),一个事件在同一个线程上创建一个frmMyDialog(在dummyForm上使用Invoke),然后使用ShowDialog:

frmMyCustomDialog.ShowDialog(_mainForm)

这会抛出一个CrossThreadException,这就是它在这一点上的样子:

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

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

3 个答案:

答案 0 :(得分: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很聪明,您可以从UI线程调用它,然后它将直接执行您的委托。 而且,请记住,在第一次使用SynchronizationContext之前,您需要创建一些WinForms表单或控件,因为当您这样做时,上下文将被初始化为适当的实现。

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

答案 1 :(得分:1)

这只是我的头脑,但正如Vladimir Perevalov在另一场讨论中所说,你必须让你的表格可见。

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

需要注意的重要一点是,InvokeRequired尝试确定所述控件的窗口句柄是否属于另一个线程。它与构造函数无关。

如果您不想显示frmDummy,可以在实例化之后立即调用CreateControl,以确保它已分配了句柄。

答案 2 :(得分:0)

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

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);
}