。ネット:バックグラウンドスレッドにメインスレッドデータが利用可能であることを知らせるにはどうすればよいですか?
-
02-07-2019 - |
質問
適切なテクニックとは何ですか スレッドA 信号 スレッドB 何かのイベントの、 スレッドB イベントが起こるのを待って座っていませんか?
共有 List<T> を埋めるバックグラウンド スレッドがあります。取得可能なデータがあることを「メイン」スレッドに非同期的に通知する方法を見つけようとしています。
EventWaitHandle オブジェクトを使用してイベントを設定することを検討しましたが、メインスレッドを Event.WaitOne() に置くことができません。
私はデリゲートのコールバックを持っていることを考えましたが、a)メインスレッドが代表者で作業をしたくない:スレッドはより多くのものを追加する仕事に戻る必要があります - 私はそれを代表者が実行する間、それを待ちたくありません、そしてb)代表者はメインスレッドにマーシャルする必要がありますが、私はUIを実行していません、私は持っていませんデリゲートを侵入するために制御します。
私は、単純にゼロ間隔の System.Windows.Forms.Timer を開始するデリゲート コールバックを検討しました (タイマーへのスレッド アクセスが同期されています)。この方法では、スレッドは呼び出し時にスタックするだけで済みます。
Timer.Enabled = true;
しかし、それはハックのようです。
昔は、私のオブジェクトは隠しウィンドウを作成し、スレッドにその隠しウィンドウの HWND にメッセージを投稿させていました。非表示のコントロールを作成することを検討しましたが、ハンドルが作成されていないコントロールでは .Invoke できないことがわかりました。さらに、UI もありません。私のオブジェクトは Web サーバー、サービス、またはコンソール上に作成された可能性がありますが、グラフィカル コントロールが表示されることは望ましくありません。また、System.Windows.Forms への依存関係をコンパイルすることも望んでいません。
オブジェクトに ISynchronizeInvoke インターフェイスを公開させることを検討しましたが、その場合は .Invoke() を実装する必要があり、それが私の問題です。
スレッド B をブロックしてイベントの発生を待機させることなく、スレッド A がスレッド B に何らかのイベントを通知する適切な手法は何ですか?
解決
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.");
}
他のヒント
ここではいくつかの回答を組み合わせています。
理想的な状況では、次のようなスレッドセーフ フラグを使用します。 AutoResetEvent
. 。電話をかけるときに無期限にブロックする必要はありません WaitOne()
, 、実際には、タイムアウトを指定できるオーバーロードがあります。このオーバーロードは返されます false
フラグが間隔中に設定されなかった場合。
あ Queue
これは、生産者/消費者の関係にとってより理想的な構造ですが、要件により、 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));
}
}
}
}
}
プロデューサーをまったくブロックしたくない場合は、少し注意が必要です。この場合、プライベートバッファとパブリックバッファの両方を備えた独自のクラスをプロデューサーに作成することをお勧めします。 AutoResetEvent
. 。プロデューサはデフォルトでアイテムをプライベート バッファに保存し、パブリック バッファに書き込もうとします。コンシューマがパブリック バッファを操作しているときは、プロデューサ オブジェクトのフラグをリセットします。プロデューサは、アイテムをプライベート バッファからパブリック バッファに移動しようとする前に、このフラグをチェックし、コンシューマが作業していない場合にのみアイテムをコピーします。
バックグラウンドワーカーを使用して 2 番目のスレッドを開始し、ProgressChanged イベントを使用して他のスレッドにデータの準備ができたことを通知する場合。その他のイベントもご用意しております。 この MSDN 記事を参照してください。.
これを行うには、正確に何をしたいかに応じて、多くの方法があります。あ プロデューサー/コンシューマーキュー おそらくあなたが望んでいることです。スレッドの優れた詳細については、次の章を参照してください。 ねじ切り (オンラインで入手可能) 優れた本より C# 3.0 の概要.
AutoResetEvent (または ManualResetEvent) を使用できます。AutoResetEvent.WaitOne(0, false) を使用するとブロックされません。例えば:
AutoResetEvent ev = new AutoResetEvent(false);
...
if(ev.WaitOne(0, false)) {
// event happened
}
else {
// do other stuff
}
この場合、BackgroundWorker クラスが答えとなります。これは、メッセージを非同期に送信できる唯一のスレッド構造です。 糸 これにより、BackgroundWorker オブジェクトが作成されました。内部的に BackgroundWorker
を使用します AsyncOperation
を呼び出してクラスを asyncOperation.Post()
方法。
this.asyncOperation = AsyncOperationManager.CreateOperation(null);
this.asyncOperation.Post(delegateMethod, arg);
.NET Framework の他のいくつかのクラスも AsyncOperation を使用します。
- 背景労働者
- SoundPlayer.LoadAsync()
- SmtpClient.SendAsync()
- Ping.SendAsync()
- WebClient.DownloadDataAsync()
- WebClient.DownloadFile()
- WebClient.DownloadFileAsync()
- ウェブクライアント...
- PictureBox.LoadAsync()
「メイン」スレッドが Windows メッセージ ポンプ (GUI) スレッドの場合は、Forms.Timer を使用してポーリングできます。GUI スレッドにワーカー スレッドからのデータを「通知」させる速度に応じてタイマー間隔を調整します。 。
共有へのアクセスを忘れずに同期してください List<>
使用する場合は foreach
, 、 避けるために CollectionModified
例外。
私はこのテクニックをリアルタイム取引アプリケーションのすべての市場データ主導の GUI 更新に使用していますが、非常にうまく機能しています。