7

バックグラウンドワーカーが同期タスクを実行したり、新しいファイルを追加したり、古いファイルを削除したりする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...
}
4

4 に答える 4

8

これを試してみると、スレッドがSTABackgroundWorkerではない(管理されたスレッドプールからのものである)ため、機能しないことがわかります。

問題の本質は、ワーカースレッド¹からユーザーインターフェイスを表示できないことです。そのため、回避する必要があります。アプリケーションの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にすることを選択できますが、スレッドプールからのものである場合、そのような可能性はありません。

于 2012-05-08T12:26:20.120 に答える
4

私は通常、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...
}
于 2012-05-08T12:38:48.417 に答える
2

これを処理するためにスレッドを起動する必要があるという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));
    }

それが役に立てば幸い。

于 2012-05-08T12:31:47.030 に答える
0

backgroundworkerを実行する前に、ダイアログを表示する必要があります。また、progresschanged-eventで、ダイアログを更新できます。

于 2012-05-08T12:37:29.460 に答える