在现有项目上工作时,我必须使用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)
时,我遇到了一个跨线程异常。
跨线程操作无效:控制从其创建的线程以外的线程访问的“ActivationConfirmationForm”。
问题:
CenterParent
时才会出现交叉线程异常?我该如何避免呢?form.InvokeRequired
总是false
?两者都可能相关!?
[编辑] @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; }
}
答案 0 :(得分:3)
查看form.InvokeRequired false 是您问题的核心。你知道它必须是真的。简单的解释是传递给ShowDialog()方法的表单对象是错误的对象。经典错误是使用 new 来创建实例,而不是使用表单对象的现有实例,即用户正在查看并在主线程上创建的实例。确保线程代码具有对该表单对象的引用,以便它可以传递正确的引用。如果你做不好,只能使用Application.OpenForms [0]。
通常,将线程代码与用户界面分离。工作线程没有显示对话框的业务。你可以使它工作,但它在实践中不能很好地工作。弹出的对话框没有用户的期望。可能发生事故,用户可能会在弹出对话框之前点击或按下按键几分之一秒。在没有看到它的情况下解除对话框。类似的CreateHandle()hack应该 not 在你的代码中。只是在用户界面准备好之前不要启动线程。由表单的Load事件发出信号。
答案 1 :(得分:1)
好吧我没有“评论”的权限,因为我是新用户,所以我只会使用这个答案空间。
您正在创建一个表单ActivationConfirmationForm的新实例,无论您使用哪个线程,此表单的创建和ShowDialog的执行都在同一个线程上下文中执行,因为这是真的,InvokeRequired(请参阅msdn)是显然会出现错误,因为您要访问的表单是在您访问它的线程中创建的。不需要使用invoke / begininvoke等。@ neriuz担心的是什么。
答案 2 :(得分:1)
表单属于与父级不同的线程。
看起来WinForms CenterParent位置参数调用WinForms .Net对象而不是使用Win32 API从HWND中查找父窗口位置,这个跨线程调用是导致跨线程异常的原因。 / p>
真正的答案是工作线程不应该有UI。他们应该给出一个结果,表明需要用户干预,主线程应该负责用户交互。
如果失败,请不要为工作线程GUI设置父窗口。如果你只有一个工作线程,它可能(只是)可行,但如果你有多个工作线程,它会引起各种混乱。
如果绝对必要,请使用P / Invoke从Win32API中查找父窗口的当前窗口位置并明确设置。
答案 3 :(得分:0)
作为跟进:
我现在有了一个有效的解决方案(如果不设置父级,对话就不是模态的)。我同意从工作线程启动对话并不是最好的事情,但在这种情况下这是一个要求。 ParentForm现在是一个Form,而不是之前的IWin32Window
在图形线程中创建对话:
private void OnActivationConfirmationRequired(DmsPackageConfiguratorCore sender, ConfigurationActivationConfirmationEventArgs args)
{
//create the dialog in the graphical thread
ActivationConfirmationForm dialog = null;
Action createDialogInGuiThread = () => dialog = new ActivationConfirmationForm(args.Data);
ParentForm.Invoke(createDialogInGuiThread);
if (dialog != null)
{
args.DoAbort = (ShowDialog(dialog) == DialogResult.No);
}
}
从UI线程调用对话
private DialogResult ShowDialog(Form form)
{
DialogResult dialogResult = DialogResult.None;
//launch the form in the graphical htread (the one of the parent form)
Action action = delegate { dialogResult = form.ShowDialog(ParentForm); };
ParentForm.Invoke(action);
return dialogResult;
}
由于所有图形内容都在UI线程中完成,因此不再需要创建句柄。