【问题标题】:Calling ShowDialog in BackgroundWorker在 BackgroundWorker 中调用 ShowDialog
【发布时间】:2012-05-08 12:23:59
【问题描述】:

我有一个 WinForms 应用程序,我的后台工作人员正在其中执行同步任务、添加新文件、删除旧文件等。

在我的后台工作人员代码中,我想向用户显示一个自定义表单,告诉他哪些内容将被删除,哪些内容将在他继续时添加,并带有是/否按钮以获取他的反馈。

我想知道是否可以在后台工作人员的 doWork 方法中执行此类操作? 如果没有,我该怎么做?

请指教..

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
   MyForm f = new MyForm();
   f.FilesToAddDelete(..);
   DialogResult result = f.ShowDialog();
   if(No...)
   return;
   else
   //keep working...
}

【问题讨论】:

    标签: c# winforms backgroundworker


    【解决方案1】:

    如果你尝试这个,你会发现它不起作用,因为BackgroundWorker 线程不是STA(它来自managed thread pool)。

    问题的本质是您无法从工作线程中显示用户界面¹,因此您必须解决它。您应该传递对应用程序 UI 元素的引用(主窗体将是一个不错的选择),然后使用 Invoke 将用户交互请求编组到您的 UI 线程。一个准系统示例:

    class MainForm
    {
    
        // all other members here
    
        public bool AskForConfirmation()
        {
            var confirmationForm = new ConfirmationForm();
            return confirmationForm.ShowDialog() == DialogResult.Yes;
        }
    }
    

    后台工作人员会这样做:

    // I assume that mainForm has been passed somehow to BackgroundWorker
    var result = (bool)mainForm.Invoke(mainForm.AskForConfirmation);
    if (result) { ... }
    

    ¹ 从技术上讲,您无法从非 STA 线程显示用户界面。如果你自己创建一个工作线程,你可以选择让它成为 STA,但如果它来自线程池,则没有这种可能性。

    【讨论】:

    • 谢谢乔恩,我会尝试一下,看看它是否适合我..你的回答很有意义..
    • 顺便说一句,有人告诉我在 BackgroundWorker 上调用 MessageBox.show(..) 是安全的,可能是因为该方法是 statis.. 它也是 unsafe() 吗?
    • @Ahmed:有人不知道他们在说什么。这里有一个相关的问题,请参阅stackoverflow.com/questions/559252/…。结论:您可以使用它,但不是因为它是static 或其他类似的垃圾原因。这是因为它专门设置了自己的消息泵。
    • 谢谢乔恩,你是真正的大师 :) 请检查我所指的关于 MessageBox 的这篇文章,原因是它是静态的,没有指出消息泵。 stackoverflow.com/questions/10283881/…
    • @Ahmed:嗯,这个答案确实没有以技术上最准确的方式表达。我认为它之所以会这样,是因为编写它的人(他无疑知道为什么它会起作用)和你之间的沟通不完善。
    【解决方案2】:

    我通常会创建一个方法来在 UI 线程上执行委托:

      private void DoOnUIThread(MethodInvoker d) {
         if (this.InvokeRequired) { this.Invoke(d); } else { d(); }
      }
    

    这样,您可以将代码更改为:

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
       DialogResult result = DialogResult.No;
       DoOnUIThread(delegate() {
          MyForm f = new MyForm();
          f.FilesToAddDelete(..);
          result = f.ShowDialog();
       });
    
       if(No...)
       return;
       else
       //keep working...
    }
    

    【讨论】:

      【解决方案3】:

      IMO 回答说您应该启动一个线程来处理这个问题是错误的。您需要将窗口跳回主调度程序线程。

      在 WPF 中

      public ShellViewModel(
          [NotNull] IWindowManager windows, 
          [NotNull] IWindsorContainer container)
      {
          if (windows == null) throw new ArgumentNullException("windows");
          if (container == null) throw new ArgumentNullException("container");
          _windows = windows;
          _container = container;
          UIDispatcher = Dispatcher.CurrentDispatcher; // not for WinForms
      }
      
      public Dispatcher UIDispatcher { get; private set; }
      

      然后,当某个事件发生在另一个线程(本例中为线程池线程)上时:

      public void Consume(ImageFound message)
      {
          var model = _container.Resolve<ChoiceViewModel>();
          model.ForImage(message);
          UIDispatcher.BeginInvoke(new Action(() => _windows.ShowWindow(model)));
      }
      

      WinForms 等效

      不要将 UIDispatcher 设置为任何值,那么您可以:

      public void Consume(ImageFound message)
      {
          var model = _container.Resolve<ChoiceViewModel>();
          model.ForImage(message);
          this.Invoke( () => _windows.ShowWindow(model) );
      }
      

      为 WPF 干燥它:

      伙计,这么多代码...

      public interface ThreadedViewModel
          : IConsumer
      {
          /// <summary>
          /// Gets the UI-thread dispatcher
          /// </summary>
          Dispatcher UIDispatcher { get; }
      }
      
      public static class ThreadedViewModelEx
      {
          public static void BeginInvoke([NotNull] this ThreadedViewModel viewModel, [NotNull] Action action)
          {
              if (viewModel == null) throw new ArgumentNullException("viewModel");
              if (action == null) throw new ArgumentNullException("action");
              if (viewModel.UIDispatcher.CheckAccess()) action();
              else viewModel.UIDispatcher.BeginInvoke(action);
          }
      }
      

      在视图模型中:

          public void Consume(ImageFound message)
          {
              var model = _container.Resolve<ChoiceViewModel>();
              model.ForImage(message);
              this.BeginInvoke(() => _windows.ShowWindow(model));
          }
      

      希望对你有帮助。

      【讨论】:

      • 说明应该启动另一个线程的答案在哪里?
      • 我从未见过这样的回答来回答我的问题.. 可能是有人创建并删除了它
      • 整个互联网。我今天自己也遇到了这个问题。 :)
      【解决方案4】:

      您应该在运行后台工作程序之前打开对话框。并且在progresschanged-event中,可以更新对话框。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多