11

ThreadBがイベントの発生を待機するのをブロックせずに、 ThreadAが何らかのイベントのThreadBにシグナルを送る適切な手法は何ですか?

共有 List<T> を埋めるバックグラウンド スレッドがあります。取得できるデータがあることを「メイン」スレッドに非同期で通知する方法を見つけようとしています。


EventWaitHandle オブジェクトを使用してイベントを設定することを検討しましたが、メイン スレッドを Event.WaitOne() に置くことができません。


私はデリゲート コールバックを持つことを検討しましたが、a) メイン スレッドがデリゲートで作業を行うことを望んでいません: スレッドは作業に戻ってさらに何かを追加する必要があります - デリゲートの実行中にスレッドを待機させたくありません。 ) デリゲートをメイン スレッドにマーシャリングする必要がありますが、UI を実行していません。デリゲートを呼び出します。


私は、ゼロ間隔のSystem.Windows.Forms.Timerを開始するデリゲートコールバックがあると考えました(タイマーへのスレッドアクセスが同期されています)。このようにして、スレッドは呼び出し時にスタックするだけで済みます

Timer.Enabled = true;

しかし、それはハックのようです。

昔は、私のオブジェクトは非表示のウィンドウを作成し、スレッドにその非表示のウィンドウの HWND にメッセージを投稿させていました。非表示のコントロールを作成することを検討しましたが、ハンドルが作成されていないコントロールで.Invokeできないことがわかりました。さらに、UI がありません。オブジェクトは、Web サーバー、サービス、またはコンソールで作成された可能性があります。グラフィカル コントロールが表示されることも、System.Windows への依存関係をコンパイルすることもありません。フォーム。


オブジェクトに ISynchronizeInvoke インターフェイスを公開させることを検討しましたが、.Invoke() を実装する必要があり、それが私の問題です。


イベントが発生するのを待ってスレッド B をブロックせずに、スレッド A に何らかのイベントのスレッド B を通知させる適切な手法は何ですか?

4

7 に答える 7

11

System.ComponentModel.BackgroundWorker クラスのコード サンプルを次に示します。

    private static BackgroundWorker worker = new BackgroundWorker();
    static void Main(string[] args)
    {
        worker.DoWork += worker_DoWork;
        worker.RunWorkerCompleted += worker_RunWorkerCompleted;
        worker.ProgressChanged += worker_ProgressChanged;
        worker.WorkerReportsProgress = true;

        Console.WriteLine("Starting application.");
        worker.RunWorkerAsync();

        Console.ReadKey();
    }

    static void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        Console.WriteLine("Progress.");
    }

    static void worker_DoWork(object sender, DoWorkEventArgs e)
    {
        Console.WriteLine("Starting doing some work now.");

        for (int i = 0; i < 5; i++)
        {
            Thread.Sleep(1000);
            worker.ReportProgress(i);
        }
    }

    static void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        Console.WriteLine("Done now.");
    }
于 2008-09-23T20:37:57.433 に答える
3

ここでいくつかの回答を組み合わせます。

理想的な状況では、 などのスレッドセーフ フラグを使用しますAutoResetEvent。を呼び出すときに無期限にブロックする必要はありませんWaitOne()。実際には、タイムアウトを指定できるオーバーロードがあります。このオーバーロードはfalse、間隔中にフラグが設定されなかった場合に返されます。

AQueueは生産者/消費者の関係にとってより理想的な構造ですが、要件によってList. 主な違いは、アイテムを抽出している間、コンシューマーがコレクションへのアクセスを確実にロックする必要があることです。最も安全な方法は、おそらくCopyToメソッドを使用してすべての要素を配列にコピーしてから、ロックを解除することです。もちろん、Listロックが保持されている間、プロデューサーが を更新しようとしないことを確認してください。

これを実装する方法を示す単純な C# コンソール アプリケーションを次に示します。タイミング間隔をいじると、さまざまなことが起こる可能性があります。この特定の構成では、消費者がアイテムをチェックする前に、プロデューサーに複数のアイテムを生成させようとしていました。

using System;
using System.Collections.Generic;
using System.Threading;

namespace ConsoleApplication1
{
    class Program
    {
        private static object LockObject = new Object();

        private static AutoResetEvent _flag;
        private static Queue<int> _list;

        static void Main(string[] args)
        {
            _list = new Queue<int>();
            _flag = new AutoResetEvent(false);

            ThreadPool.QueueUserWorkItem(ProducerThread);

            int itemCount = 0;

            while (itemCount < 10)
            {
                if (_flag.WaitOne(0))
                {
                    // there was an item
                    lock (LockObject)
                    {
                        Console.WriteLine("Items in queue:");
                        while (_list.Count > 0)
                        {
                            Console.WriteLine("Found item {0}.", _list.Dequeue());
                            itemCount++;
                        }
                    }
                }
                else
                {
                    Console.WriteLine("No items in queue.");
                    Thread.Sleep(125);
                }
            }
        }

        private static void ProducerThread(object state)
        {
            Random rng = new Random();

            Thread.Sleep(250);

            for (int i = 0; i < 10; i++)
            {
                lock (LockObject)
                {
                    _list.Enqueue(rng.Next(0, 100));
                    _flag.Set();
                    Thread.Sleep(rng.Next(0, 250));
                }
            }
        }
    }
}

プロデューサーをまったくブロックしたくない場合は、もう少し注意が必要です。この場合、プライベート バッファーとパブリック バッファーの両方と public を使用して、プロデューサーを独自のクラスにすることをお勧めしAutoResetEventます。プロデューサは、デフォルトでアイテムをプライベート バッファに格納し、次にそれらをパブリック バッファに書き込もうとします。コンシューマがパブリック バッファを操作している場合、プロデューサ オブジェクトのフラグをリセットします。プロデューサはアイテムをプライベート バッファからパブリック バッファに移動しようとする前に、このフラグをチェックし、コンシューマが作業していないときにのみアイテムをコピーします。

于 2008-09-23T20:37:46.420 に答える
1

backgroundworkerを使用して2番目のスレッドを開始し、ProgressChangedイベントを使用して、データの準備ができたことを他のスレッドに通知する場合。他のイベントも利用できます。 このMSDNの記事で、始めることができます

于 2008-09-23T18:45:03.320 に答える
1

正確に何をしたいかに応じて、これを行うには多くの方法があります。生産者/消費者キューはおそらくあなたが望むものです。スレッドの詳細については、優れた本C#3.0 in a Nutshellのスレッド(オンラインで入手可能)の章を参照してください。

于 2008-09-23T18:48:18.947 に答える
1

AutoResetEvent (または ManualResetEvent) を使用できます。AutoResetEvent.WaitOne(0, false) を使用すると、ブロックされません。例えば:

AutoResetEvent ev = new AutoResetEvent(false);
...
if(ev.WaitOne(0, false)) {
  // event happened
}
else {
 // do other stuff
}
于 2008-09-23T19:24:47.877 に答える
1

この場合、BackgroundWorker クラスが答えです。これは、BackgroundWorker オブジェクトを作成したスレッドに非同期でメッセージを送信できる唯一のスレッド構造です。メソッドを呼び出してクラスを内部的BackgroundWorkerに使用します。AsyncOperationasyncOperation.Post()

this.asyncOperation = AsyncOperationManager.CreateOperation(null);
this.asyncOperation.Post(delegateMethod, arg);

.NET フレームワークの他のいくつかのクラスも AsyncOperation を使用します。

  • バックグラウンドワーカー
  • SoundPlayer.LoadAsync()
  • SmtpClient.SendAsync()
  • Ping.SendAsync()
  • WebClient.DownloadDataAsync()
  • WebClient.DownloadFile()
  • WebClient.DownloadFileAsync()
  • ウェブクライアント...
  • PictureBox.LoadAsync()
于 2008-09-23T21:42:15.423 に答える
0

「メイン」スレッドが Windows メッセージ ポンプ (GUI) スレッドである場合は、Forms.Timer を使用してポーリングできます。GUI スレッドにワーカー スレッドからのデータを「通知」させる必要がある速さに応じて、タイマー間隔を調整します。 .

List<>を使用する場合は、例外foreachを避けるために共有へのアクセスを同期することを忘れないでください。CollectionModified

私はこの手法をリアルタイム取引アプリケーションのすべての市場データ主導の GUI 更新に使用しており、非常にうまく機能しています。

于 2008-09-23T20:14:44.807 に答える