40

次のコードを観察してください。

var handler = GetTheRightHandler();
var bw = new BackgroundWorker();
bw.RunWorkerCompleted += OnAsyncOperationCompleted;
bw.DoWork += OnDoWorkLoadChildren;
bw.RunWorkerAsync(handler);

bwここで、作業が完了するまで待ちたいとします。そうする正しい方法は何ですか?

私の解決策はこれです:

bool finished = false;
var handler = GetTheRightHandler();
var bw = new BackgroundWorker();
bw.RunWorkerCompleted += (sender, args) =>
{
  OnAsyncOperationCompleted(sender, args);
  finished = true;
});
bw.DoWork += OnDoWorkLoadChildren;
bw.RunWorkerAsync(handler);
int timeout = N;
while (!finished && timeout > 0)
{
  Thread.Sleep(1000);
  --timeout;
}
if (!finished)
{
  throw new TimedoutException("bla bla bla");
}

でも、私は嫌いです。

finishedフラグを同期イベントに置き換えて、RunWorkerCompletedハンドラーに設定し、スリープ中のループを実行する代わりに後でブロックすることを検討しました。

残念ながら、コードは WPF または WindowsForm 同期コンテキストで実行される可能性があるため、これは間違っていますRunWorkerCompleted

より良い解決策を知りたいです。

ありがとう。

編集:

PS

  • サンプルコードは、私の質問を明確にするために意図的に工夫されています。私は完了コールバックを完全に認識していますが、完了まで待つ方法を知りたいです。それが私の質問です。
  • 私は、、、などを認識していThread.JoinますDelegate.BeginInvoke...ThreadPool.QueueUserWorkItem質問は具体的には に関するものBackgroundWorkerです。

編集2:

わかりました。シナリオを説明すると、はるかに簡単になると思います。

いくつかの非同期コードを呼び出す単体テスト メソッドがあり、最終的にBackgroundWorker完了ハンドラーを渡すことができる a に関与します。すべてのコードは私のものなので、必要に応じて実装を変更できます。ただし、 を置き換えるつもりはありませんBackgroundWorker。適切な同期コンテキストが自動的に使用されるため、コードが UI スレッドで呼び出されると、完了コールバックが同じ UI スレッドで呼び出されます。これは非常に優れています。

いずれにせよ、BW が作業を完了する前に単体テスト メソッドが終了する可能性はありますが、これは良くありません。そのため、BW が完了するまで待ちたいのですが、最善の方法を知りたいと考えています。

それにはもっと多くの部分がありますが、全体像は多かれ少なかれ今説明したようなものです.

4

10 に答える 10

46

次のように AutoResetEvent クラスを使用してみてください。

var doneEvent = new AutoResetEvent(false);
var bw = new BackgroundWorker();

bw.DoWork += (sender, e) =>
{
  try
  {
    if (!e.Cancel)
    {
      // Do work
    }
  }
  finally
  {
    doneEvent.Set();
  }
};

bw.RunWorkerAsync();
doneEvent.WaitOne();

注意:doneEvent.Set()何が起こっても が呼び出されることを確認する必要があります。またdoneEvent.WaitOne()、タイムアウト期間を指定する引数を に提供することもできます。

注:このコードは、同様の質問に対するFredrik Kalsethの回答のほとんどのコピーです。

于 2009-08-26T11:07:50.460 に答える
19

バックグラウンドワーカースレッド(単一または複数)を待機するには、次の手順を実行します。

  1. プログラムで作成したバックグラウンドワーカーのリストを作成します。

    private IList<BackgroundWorker> m_WorkersWithData = new List<BackgroundWorker>();
    
  2. リストにバックグラウンドワーカーを追加します。

    BackgroundWorker worker = new BackgroundWorker();
    worker.DoWork += new DoWorkEventHandler(worker_DoWork);
    worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
    worker.WorkerReportsProgress = true;
    m_WorkersWithData.Add(worker);
    worker.RunWorkerAsync();
    
  3. 次の関数を使用して、リスト内のすべてのワーカーを待機します。

    private void CheckAllThreadsHaveFinishedWorking()
    {
        bool hasAllThreadsFinished = false;
        while (!hasAllThreadsFinished)
        {
            hasAllThreadsFinished = (from worker in m_WorkersWithData
                                     where worker.IsBusy
                                     select worker).ToList().Count == 0;
            Application.DoEvents(); //This call is very important if you want to have a progress bar and want to update it
                                    //from the Progress event of the background worker.
            Thread.Sleep(1000);     //This call waits if the loop continues making sure that the CPU time gets freed before
                                    //re-checking.
        }
        m_WorkersWithData.Clear();  //After the loop exits clear the list of all background workers to release memory.
                                    //On the contrary you can also dispose your background workers.
    }
    
于 2011-10-11T11:38:03.293 に答える
7

BackgroundWorker には完了イベントがあります。待機する代わりに、完了ハンドラーから残りのコード パスを呼び出します。

于 2009-08-26T08:04:09.847 に答える
2

VB.NET

While BackgroundWorker1.IsBusy()
    Windows.Forms.Application.DoEvents()
End While

これを使用して、複数のイベントを連鎖させることができます。(疑似コードに従う)

download_file("filepath")

    While BackgroundWorker1.IsBusy()
       Windows.Forms.Application.DoEvents()
    End While
'Waits to install until the download is complete and lets other UI events function install_file("filepath")
While BackgroundWorker1.IsBusy()
    Windows.Forms.Application.DoEvents()
End While
'Waits for the install to complete before presenting the message box
msgbox("File Installed")
于 2013-08-22T18:46:46.473 に答える
2

backgrWorker.IsBusyでループをチェックインするのは適切な方法でApplication.DoEvents()はありません。

@JohannesHに同意します。AutoResetEventをエレガントなソリューションとして確実に使用する必要があります。ただし、UI スレッドで使用しないと、メイン スレッドがブロックされます。別のバックグラウンド ワーカー スレッドから取得する必要があります。

AutoResetEvent aevent = new AutoResetEvent(false);    
private void button1_Click(object sender, EventArgs e)
{
    bws = new BackgroundWorker();
    bws.DoWork += new DoWorkEventHandler(bw_work);
    bws.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_complete);
    bws.RunWorkerAsync();

    bwWaiting.DoWork += new DoWorkEventHandler(waiting_work);
    bwWaiting.RunWorkerCompleted += new RunWorkerCompletedEventHandler(waiting_complete);
    bwWaiting.RunWorkerAsync();
}

void bw_work(object sender, DoWorkEventArgs e)
{
    Thread.Sleep(2000);
}

void bw_complete(object sender, RunWorkerCompletedEventArgs e)
{
    Debug.WriteLine("complete " + bwThread.ToString());
    aevent.Set();
}
void waiting_work(object sender, DoWorkEventArgs e)
{
    aevent.WaitOne();
}

void waiting_complete(object sender, RunWorkerCompletedEventArgs e)
{
    Debug.WriteLine("complete waiting thread");
}
于 2015-03-19T04:47:10.967 に答える
2

この質問は古いですが、著者が探していた答えが得られたとは思いません。

これは少し汚れており、vb.NET にありますが、私にとってはうまくいきます

Private Sub MultiTaskingForThePoor()
    Try
        'Start background worker
        bgwAsyncTasks.RunWorkerAsync()
        'Do some other stuff here
        For i as integer = 0 to 100
            lblOutput.Text = cstr(i)
        Next

        'Wait for Background worker
        While bgwAsyncTasks.isBusy()
            Windows.Forms.Application.DoEvents()
        End While

        'Voila, we are back in sync
        lblOutput.Text = "Success!"
    Catch ex As Exception
        MsgBox("Oops!" & vbcrlf & ex.Message)
    End Try
End Sub
于 2011-12-02T13:51:00.443 に答える
0

待っているとはどういう意味かよくわかりません。何かを (BW によって) 実行した後、別のことを実行したいということですか? あなたと同じように bw.RunWorkerCompleted を使用し(読みやすくするために別の関数を使用します)、そのコールバック関数で次のことを行います。タイマーを開始して、作業に時間がかかりすぎないかどうかを確認します。

var handler = GetTheRightHandler();
var bw = new BackgroundWorker();
bw.RunWorkerCompleted += (sender, args) =>
{
  OnAsyncOperationCompleted(sender, args);
});
bw.DoWork += OnDoWorkLoadChildren;
bw.RunWorkerAsync(handler);

Timer Clock=new Timer();
Clock.Interval=1000;
Clock.Start();
Clock.Tick+=new EventHandler(Timer_Tick);

public void Timer_Tick(object sender,EventArgs eArgs)
{   
    if (bw.WorkerSupportsCancellation == true)
    {
        bw.CancelAsync();
    }

    throw new TimedoutException("bla bla bla");
 }

OnDoWorkLoadChildren で:

if ((worker.CancellationPending == true))
{
    e.Cancel = true;
    //return or something
}
于 2009-08-26T12:03:23.860 に答える
0

また、適切なソリューションを探していました。排他ロックで待ちを解消しました。コードのクリティカル パスは、パブリック コンテナー (ここではコンソール) への書き込みと、ワーカーの増減です。この変数への書き込み中にスレッドが干渉することはありません。そうしないと、カウントが保証されなくなります。

public class Program
{
    public static int worker = 0;
    public static object lockObject = 0;

    static void Main(string[] args)
    {

        BackgroundworkerTest backgroundworkerTest = new BackgroundworkerTest();
        backgroundworkerTest.WalkDir("C:\\");
        while (backgroundworkerTest.Worker > 0)
        {
            // Exclusive write on console
            lock (backgroundworkerTest.ExclusiveLock)
            {
                Console.CursorTop = 4; Console.CursorLeft = 1;
                var consoleOut = string.Format("Worker busy count={0}", backgroundworkerTest.Worker);
                Console.Write("{0}{1}", consoleOut, new string(' ', Console.WindowWidth-consoleOut.Length));
            }
        }
    }
}

public class BackgroundworkerTest
{
    private int worker = 0;
    public object ExclusiveLock = 0;

    public int Worker
    {
        get { return this.worker; }
    }

    public void WalkDir(string dir)
    {
        // Exclusive write on console
        lock (this.ExclusiveLock)
        {
            Console.CursorTop = 1; Console.CursorLeft = 1;
            var consoleOut = string.Format("Directory={0}", dir);
            Console.Write("{0}{1}", consoleOut, new string(' ', Console.WindowWidth*3 - consoleOut.Length));
        }

        var currentDir = new System.IO.DirectoryInfo(dir);
        DirectoryInfo[] directoryList = null;
        try
        {
            directoryList = currentDir.GetDirectories();
        }
        catch (UnauthorizedAccessException unauthorizedAccessException)
        {
            // No access to this directory, so let's leave
            return;
        }

        foreach (var directoryInfo in directoryList)
        {
            var bw = new BackgroundWorker();

            bw.RunWorkerCompleted += (sender, args) =>
            {
                // Make sure that this worker variable is not messed up
                lock (this.ExclusiveLock)
                {
                    worker--;
                }
            };

            DirectoryInfo info = directoryInfo;
            bw.DoWork += (sender, args) => this.WalkDir(info.FullName);

            lock (this.ExclusiveLock)
            {
                // Make sure that this worker variable is not messed up
                worker++;
            }
            bw.RunWorkerAsync();
        }
    }
}
于 2014-09-22T15:18:06.263 に答える
0

私はBackgroundWorkerでTasksを使用しました

任意の数のタスクを作成して、タスクのリストに追加できます。ワーカーは、タスクが追加されると開始し、ワーカーが IsBusy のときにタスクが追加されると再起動し、タスクがなくなると停止します。

これにより、GUI をフリーズすることなく、必要なだけ非同期で更新できます。

これは私にとってはそのまま機能します。

    // 'tasks' is simply List<Task> that includes events for adding objects
    private ObservableCollection<Task> tasks = new ObservableCollection<Task>();
    // this will asynchronously iterate through the list of tasks 
    private BackgroundWorker task_worker = new BackgroundWorker();

    public Form1()
    {
        InitializeComponent();
        // set up the event handlers
        tasks.CollectionChanged += tasks_CollectionChanged;
        task_worker.DoWork += task_worker_DoWork;
        task_worker.RunWorkerCompleted += task_worker_RunWorkerCompleted;
        task_worker.WorkerSupportsCancellation = true;

    }

    // ----------- worker events
    void task_worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if (tasks.Count != 0)
        {
            task_worker.RunWorkerAsync();
        }
    }

    void task_worker_DoWork(object sender, DoWorkEventArgs e)
    {
        try
        {

            foreach (Task t in tasks)
            {
                t.RunSynchronously();
                tasks.Remove(t);
            }
        }
        catch
        {
            task_worker.CancelAsync();
        }
    }


    // ------------- task event
    // runs when a task is added to the list
    void tasks_CollectionChanged(object sender,
        System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        if (!task_worker.IsBusy)
        {
            task_worker.RunWorkerAsync();
        }
    }

ここで必要なのは、新しいタスクを作成して List<> に追加することだけです。List<> に配置された順序でワーカーによって実行されます。

Task t = new Task(() => {

        // do something here
    });

    tasks.Add(t);
于 2017-01-20T13:59:38.883 に答える
-1

OpenCV には関数 WaitKey が存在します。Ir では、この問題を次の方法で解決できます。

while (this->backgroundWorker1->IsBusy) {
    waitKey(10);
    std::cout << "Wait for background process: " << std::endl;
}
this->backgroundWorker1->RunWorkerAsync();
于 2014-05-07T11:37:45.597 に答える