在我的应用程序中,我启动了一个隐藏的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
的值相同吗?这里发生了什么?
我已经检查过在创建第一个实例后根本没有重新创建frmStart
和dummyForm
。
编辑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?
答案 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);
}