使工作者与UI线程同步

时间:2012-01-26 12:47:25

标签: c# winforms multithreading

在现有项目上工作时,我必须使用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); };

其中ParentWindowIWin32Window的实例,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; }
}

4 个答案:

答案 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线程中完成,因此不再需要创建句柄。