C# 线程:等待窗口打开以执行操作
我已经为我的程序编写了一个窗口管理器,它在程序的整个生命周期中(在后台线程上)保持某些窗口的打开状态(如果用户希望打开它们)。 我刚刚为联系人窗口实现了一个操作。问题是,当窗口已打开时,该操作会起作用,但如果在窗口尚未打开时调用该操作,则窗口将打开,但该操作未执行(再次按下按钮将执行该操作) 守则:C# 线程:等待窗口打开以执行操作,c#,multithreading,C#,Multithreading,我已经为我的程序编写了一个窗口管理器,它在程序的整个生命周期中(在后台线程上)保持某些窗口的打开状态(如果用户希望打开它们)。 我刚刚为联系人窗口实现了一个操作。问题是,当窗口已打开时,该操作会起作用,但如果在窗口尚未打开时调用该操作,则窗口将打开,但该操作未执行(再次按下按钮将执行该操作) 守则: private static SetupContacts _contactsWindow; private static Thread _contactthread; public static
private static SetupContacts _contactsWindow;
private static Thread _contactthread;
public static void ShowContact(repUserObject uo, ContactFormAction action, int contactID)
{
if (_contactsWindow == null)
CreateContactThread(uo, contactID);
// make sure it is still alive
if (!_contactthread.IsAlive)
CreateContactThread(uo, contactID);
if (_contactsWindow != null)
{
_contactsWindow.BringToFront();
_contactsWindow.Focus();
switch (action)
{
case ContactFormAction.ViewContact:
if (contactID > 0)
_contactsWindow.LoadCustomer(contactID); // load the contact
break;
case ContactFormAction.AddNewContact:
_contactsWindow.AddCustomer();
break;
}
}
}
private static void CreateContactThread(repUserObject uo, int contactID)
{
if (_contactthread == null || !_contactthread.IsAlive)
{
_contactthread = new Thread(delegate()
{
_contactsWindow = new SetupContacts(uo, contactID);
_contactsWindow.CerberusContactScreenClosed += delegate { _contactsWindow = null; };
_contactsWindow.CerberusContactHasBeenSaved += delegate(object sender, ContactBeenSavedEventArgs args)
{
if (CerberusContactHasBeenSaved != null)
CerberusContactHasBeenSaved.Raise(sender, args);
};
Application.EnableVisualStyles();
BonusSkins.Register();
SkinManager.EnableFormSkins();
UserLookAndFeel.Default.SetSkinStyle("iMaginary");
Application.Run(_contactsWindow);
});
_contactthread.SetApartmentState(ApartmentState.STA);
_contactthread.Start();
}
}
当例程第一次运行(通过调用ShowTime)时会发生什么情况,即它命中第一个if语句并转到CreateContactThread()例程。这就完成了任务,但当它返回时,_contactsWindow仍然为空。下一次调用例程时(即第二次按下按钮进行调用),由于_contactWindow不为空,因此一切正常。
如何让它一次完成所有任务?我与评论员Blorgbeard意见一致,他建议运行多个UI线程是个坏主意。当在单个线程中使用API时,API本身工作得最好,并且在单个线程中最容易处理在代码中对UI对象执行的许多类型的操作和操作,因为这样做本质上确保了事情按照预期的顺序发生(例如,变量在使用前已初始化) 也就是说,如果出于某种原因,您确实必须在不同的线程中运行新窗口,那么您可以同步这两个线程,以便初始线程无法继续,直到新线程达到足够的程度,使您希望在新初始化的对象上执行的操作有合理的成功机会(当然,包括最初创建的对象) 有很多技术可以同步线程,但我更喜欢新的
TaskCompletionSource
对象。它使用起来很简单,如果您将代码更新为使用async
/wait
,它将很容易与之匹配
例如:
public static void ShowContact(repUserObject uo, ContactFormAction action, int contactID)
{
CreateContactThread(uo, contactID);
if (_contactsWindow != null)
{
_contactsWindow.BringToFront();
_contactsWindow.Focus();
switch (action)
{
case ContactFormAction.ViewContact:
if (contactID > 0)
_contactsWindow.LoadCustomer(contactID); // load the contact
break;
case ContactFormAction.AddNewContact:
_contactsWindow.AddCustomer();
break;
}
}
}
private static void CreateContactThread(repUserObject uo, int contactID)
{
if (_contactthread == null || !_contactthread.IsAlive)
{
TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
_contactthread = new Thread(delegate()
{
_contactsWindow = new SetupContacts(uo, contactID);
_contactsWindow.CerberusContactScreenClosed += delegate { _contactsWindow = null; };
_contactsWindow.CerberusContactHasBeenSaved += delegate(object sender, ContactBeenSavedEventArgs args)
{
if (CerberusContactHasBeenSaved != null)
CerberusContactHasBeenSaved.Raise(sender, args);
};
_contactsWindow.Loaded += (sender, e) =>
{
tcs.SetResult(true);
};
Application.EnableVisualStyles();
BonusSkins.Register();
SkinManager.EnableFormSkins();
UserLookAndFeel.Default.SetSkinStyle("iMaginary");
Application.Run(_contactsWindow);
});
_contactthread.SetApartmentState(ApartmentState.STA);
_contactthread.Start();
tcs.Task.Wait();
}
}
public static void ShowContact(RepuseObject uo、ContactFormAction动作、int contactID)
{
CreateContactThread(uo,contactID);
如果(_contactsWindow!=null)
{
_contactsWindow.BringToFront();
_contactsWindow.Focus();
开关(动作)
{
案例联系人FormAction.ViewContact:
如果(联系人ID>0)
_contactsWindow.LoadCustomer(contactID);//加载联系人
打破
案例ContactFormAction.AddNewContact:
_contactsWindow.AddCustomer();
打破
}
}
}
私有静态void CreateContactThread(RepuseObject uo,int contactID)
{
如果(_contactthread==null | |!_contactthread.IsAlive)
{
TaskCompletionSource tcs=新的TaskCompletionSource();
_contactthread=新线程(委托()
{
_contactsWindow=新设置的联系人(uo,联系人ID);
_contactsWindow.CerberusContactScreenClosed+=委托{u contactsWindow=null;};
_contactsWindow.CerberusContactHasBeenSaved+=委托(对象发送者,ContactBeenSavedEventArgs参数)
{
如果(CerberusContactHasBeenSaved!=null)
CerberusContactHasBeenSaved.Raise(发送方,args);
};
_contactsWindow.Loaded+=(发件人,e)=>
{
tcs.SetResult(真);
};
Application.EnableVisualStyles();
寄存器();
SkinManager.EnableFormSkins();
UserLookAndFeel.Default.SetSkinStyle(“虚构”);
应用程序运行(_contactsWindow);
});
_SetApartmentState(ApartmentState.STA);
_contactthread.Start();
Task.Wait();
}
}
注意事项:
- 在我看来,您的代码中存在冗余检查。
方法本身检查CreateContactThread()
和null
,如果其中任何一个为false,则重新启动线程。因此,理论上,在该方法返回时,调用方应确保所有内容都已按需初始化。并且您只需调用该方法一次。因此,我将代码更改为:只调用该方法一次,并且无条件地这样做(因为如果没有什么要做的,该方法将什么也不做)!IsAlive
- 调用线程将在
方法启动新线程后,直到引发新窗口的CreateContactThread()中等待
加载事件为止。当然,窗口对象本身的创建时间早于此,您实际上可以在那时释放调用线程。但在我看来,您可能希望在开始尝试之前完全初始化窗口对象对它做点什么。所以我把同步延迟到了那个点
- 正如Blorgbeard所指出的,在多个线程中运行UI对象的风险之一是,如果不获得
s,就很难访问这些对象。即使它有效,您也不应该真正访问创建它的线程之外的InvalidOperationException
,但上面的代码就是这样做的(即从原始线程调用\u contactsWindow
,BringToFront()
,Focus()
,和LoadCustomer()
)。我不能保证上面的代码实际上是完全正确的。只是它解决了您所询问的主要同步问题AddCustomer()
- 说到其他可能的bug,您可能有一个未解决的争用条件,即新联系人窗体线程可能正在退出,就像您正在检查它的
属性一样。如果您在它退出之前检查该属性,但在它退出后尝试访问该线程和/或窗口,那么您的代码可能会失败IsAlive