在现有的项目中,我必须使用WinForms(一段时间没有使用它),并且与UI线程同步有一个问题.
我必须与以下工作集成的设计:BackgroundWorker获取一个Action作为参数,并以异步方式执行.我正在开展的行动有两部分:一个核心类(包含业务逻辑)和一个GUI部分,通过事件通知核心,如果它必须请求用户交互.
if (!IsHandleCreated) { //be sure to create the handle in the constructor //to allow synchronization with th GUI thread //when using Show() or ShowDialog() CreateHandle(); }
这样,以下代码工作:
private DialogResult ShowDialog(Form form) { DialogResult dialogResult = DialogResult.None; Action action = delegate { dialogResult = form.ShowDialog(); }; form.Invoke(action); return dialogResult; }
对于此示例,启动位置已设置为Windows默认值.
如果我把它改成:
Action action = delegate { dialogResult = form.ShowDialog(ParentWindow); };
其中ParentWindow是IWin32Window的实例,并且WindowStartupLocation设置为CenterParent.调用form.Invoke(action)时,我得到一个跨线程异常.
Cross-thread operation not valid: Control ‘ActivationConfirmationForm’ accessed from a thread other than the thread it was created on.
问题:
>为什么只有在将启动位置设置为CenterParent时,才会出现跨线程异常?我该如何避免呢?
>为什么form.Invokerequired总是假的?
两者都可能相关!
[编辑]
@Reniuz:
你没有在这里丢失任何东西;)
该呼叫正在由核心通知的听众进行
private static void OnActivationConfirmationrequired(DmsPackageConfiguratorCore sender,ConfigurationActivationConfirmationEventArgs args) { args.DoAbort = (ShowDialog(new ActivationConfirmationForm(args.Data)) == DialogResult.No); }
我处理的一切都在GUI界面
/// <summary> /// Interface defining methods and properties used to show dialogs while performing package specific operations /// </summary> public interface IPackageConfiguratorGui { /// <summary> /// Gets or sets the package configurator core. /// </summary> /// <value>The package configurator core.</value> IPackageConfiguratorCore PackageConfiguratorCore { get; set; } /// <summary> /// Gets or sets the parent window. /// </summary> /// <value>The parent window.</value> IWin32Window ParentWindow { get; set; } /// <summary> /// Gets the package identifier. /// </summary> /// <value>The package identifier.</value> PackageIdentifier PackageIdentifier { get; } }
解决方法
看到form.Invokerequired在false是你的问题的核心.你知道它必须是真实的.简单的解释是传递给您的ShowDialog()方法的表单对象是错误的对象.经典的错误是使用new来创建实例,而不是使用窗体对象的现有实例,即用户正在查看并在主线程上创建的实例.确保线程代码具有对该表单对象的引用,以便它可以传递正确的引用.只有使用Application.OpenForms [0],如果你不能正确的话.
一般来说,将线程代码与用户界面分离.工作线程没有显示对话的业务.你可以使它工作,但它在实践中不能很好地工作.弹出对话框,无需用户期待.发生事故可能,用户可能会在弹出对话框之前点击或按下键几分之一秒.关闭对话框,甚至不看它. CreateHandle()hack同样不应该在您的代码中.只需在用户界面准备好之前不要启动线程.由表单的Load事件发出信号.