【问题标题】:Synchronizing worker with UI thread将工作线程与 UI 线程同步
【发布时间】:2012-02-19 13:23:14
【问题描述】:

在处理现有项目时,我必须使用 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

两者可能是相关的!?

[编辑] @雷纽兹: 你不会在这里错过任何东西;) 呼叫是由核心通知的侦听器发出的

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; }
}

【问题讨论】:

  • 是因为CenterParent 还是因为您实际上正在设置父窗口而触发跨线程异常(即,如果您删除CenterParent 设置,它仍然会触发吗)?
  • @Strillo 在设置 ParentWindow 时总是会触发
  • 表单调用来显示自己?或者我在这里遗漏了什么?你能发布所有代码吗?
  • @Reniuz 我已经在帖子本身中添加了答案(出于格式原因......)
  • 我认为问题可能是您试图访问一个没有句柄的窗口(ParentWindow)。如果您尝试公开父窗口的 CreateHandle 方法,然后在构造函数中或执行操作之前在子代码中调用它怎么办?

标签: c# winforms multithreading


【解决方案1】:

false 处看到 form.InvokeRequired 是您问题的核心。你知道它必须是真的。简单的解释是传递给 ShowDialog() 方法的表单对象是错误的对象。经典错误是使用 new 来创建实例,而不是使用表单对象的现有实例,用户正在查看并在主线程上创建的实例。确保线程代码具有对该表单对象的引用,以便它可以传递正确的引用。如果您不能正确使用,请仅使用 Application.OpenForms[0]。

通常,将线程代码与用户界面分离。工作线程没有显示对话框的业务。您可以使其工作,但在实践中效果不佳。对话框会在用户没有预料到的情况下弹出。在弹出对话框之前,用户可能会在几分之一秒内单击或按下某个键,这可能会发生事故。关闭对话框,甚至没有看到它。 CreateHandle() hack 同样应该在你的代码中。在用户界面准备好之前不要启动线程。由表单的 Load 事件发出信号。

【讨论】:

  • 感谢您的解释!表单实际上是在工作人员的 GUI 部分中动态创建的,而不是由 GUI 线程创建的。这是我无法改变的设计问题。用户将不得不忍受在 Windows 默认位置弹出的对话框;)
【解决方案2】:

好的,因为我是新用户,所以我无权“评论”,所以我将使用这个答案空间。

您正在创建表单 ActivationConfirmationForm 的新实例,无论您在哪个线程中,此表单的创建和 ShowDialog 的执行都在同一个线程上下文中执行,因为这是真的,InvokeRequired(参见 msdn)是显然会是错误的,因为您要访问的表单是在您访问它的线程中创建的。无需使用 invoke/begininvoke 等。@reniuz 担心的那种。

【讨论】:

    【解决方案3】:

    表单属于与父级不同的线程。

    看来,WinForms CenterParent 位置参数调用了 WinForms .Net 对象,而不是使用 Win32 API 从 HWND 中查找父窗口位置,而这种跨线程调用是导致跨线程异常的原因。

    真正的答案是工作线程不应该有 UI。他们应该给出一个结果,表明需要用户干预,并且主线程应该负责用户交互。

    否则,不要为工作线程 GUI 设置父窗口。如果你只有一个工作线程,它可能(只是)可行,但如果你有多个工作线程,它会引起各种混乱。

    如果必须,请使用 P/Invoke 从 Win32API 中找到父窗口的当前窗口位置并显式设置。

    【讨论】:

      【解决方案4】:

      作为后续行动:
      我现在有了一个可行的解决方案(如果没有设置父级,对话就不是模态的)。我同意从工作线程启动对话不是最好的事情,但在这种情况下,这是一个要求。 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 线程中完成,因此不再需要创建句柄。

      【讨论】:

        猜你喜欢
        • 2021-01-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-08-29
        • 1970-01-01
        • 2012-01-17
        • 2013-10-09
        • 2016-03-12
        相关资源
        最近更新 更多