C# 在 BackgroundWorker 中调用 ShowDialog

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10498555/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-09 14:05:06  来源:igfitidea点击:

Calling ShowDialog in BackgroundWorker

c#winformsbackgroundworker

提问by Ahmed

I have a WinForms application in which my background worker is doing a sync task, adding new files, removing old ones etc.

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

In my background worker code I want to show a custom form to user telling him what will be deleted and what will be added if he continues, with YES/NO buttons to get his feedback.

在我的后台工作代码中,我想向用户显示一个自定义表单,告诉他将删除哪些内容以及如果他继续操作将添加哪些内容,并使用“是”/“否”按钮来获取他的反馈。

I was wondering if it is ok to do something like this in background worker's doWork method? If not, how should I do it?

我想知道在后台工作人员的 doWork 方法中做这样的事情是否可以?如果没有,我该怎么做?

Please advise..

请指教..

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

采纳答案by Jon

If you try this you will see for yourself that it will not work because the BackgroundWorkerthread is not STA(it comes from the managed thread pool).

如果您尝试这样做,您会亲眼看到它不会工作,因为该BackgroundWorker线程不是STA(它来自托管线程池)。

The essence of the matter is that you cannot show user interface from a worker thread1, so you must work around it. You should pass a reference to a UI element of your application (the main form would be a good choice) and then use Invoketo marshal a request for user interaction to your UI thread. A barebones example:

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

class MainForm
{

    // all other members here

    public bool AskForConfirmation()
    {
        var confirmationForm = new ConfirmationForm();
        return confirmationForm.ShowDialog() == DialogResult.Yes;
    }
}

And the background worker would do this:

后台工作人员会这样做:

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


1 Technically, you cannot show user interface from a thread that is not STA. If you create a worker thread yourself you can choose to make it STA anyway, but if it comes from the thread pool there is no such possibility.

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

回答by Henrik

IMO answers stating that you should launch a thread to handle this are misguided. What you need is to jump the window back to the main dispatcher thread.

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

In WPF

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

and then, when some event occurs on another thread (thread pool thread in this case):

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

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

WinForms equivalent

WinForms 等效项

Don't set UIDispatcher to anything, then you can do have:

不要将 UIDispatcher 设置为任何内容,然后您可以:

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

DRYing it up for WPF:

为 WPF 干燥:

Man, so much code...

伙计,这么多代码......

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

and in the view model:

并在视图模型中:

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

Hope it helps.

希望能帮助到你。

回答by Tomtom

You should bring up the dialog before you run the backgroundworker. And in the progresschanged-event, you can update the dialog.

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

回答by C-Pound Guru

I usually create a method to execute a delegate on the UI thread:

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

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

With this, you can change your code to:

有了这个,您可以将代码更改为:

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