私のメインGUIスレッドと、その内部で実行されている2番目のスレッドは、独自のApplicationContextです(実行する作業がない場合でも、それを維持するため)。 GUIスレッドから2番目のスレッドのメソッドを呼び出したいのですが、thread.Method();を呼び出すだけの場合メインGUIスレッドで実行されているようで、GUIが応答しなくなります。異なるスレッドでメソッドを呼び出す最良の方法は何ですか?
pdate:ここで私が本当に期待しているのは、GUIとの通信ではなく、2つのスレッド間の通信です。 GUIは、たまたま私の2番目のスレッドと通信する必要があるスレッドの1つです。
アップデート#2:わかりました、本当に何かが足りないはずです。イベントとデリゲートを作成し、ワーカースレッドにイベントをサブスクライブさせました。しかし、Invoke(MyEvent);を呼び出すと、私のGUIスレッドから、ワーカースレッドが行う作業は最終的にGUIスレッド上にあり、処理が完了するまでGUIスレッドをハングさせます。静的オブジェクトをポーリングせずに、私がやろうとしていることは可能ですか?
.Netには、バックグラウンドタスクの実行とGUIとの通信を処理するためのSystem.ComponentModel.BackgroundWorker
クラスがすでに付属しています。これを使って。
うわー、どうやって人々が質問を読まなかったのか信じられません。
とにかく、これは私がすることです。
2つのスレッド間でオブジェクトを共有しないようにしてください。 GUIスレッドがキューにメッセージを貼り付けると、GUIスレッドはメッセージを所有しなくなります。メッセージへの参照を保持できない場合、問題が発生します。
これは可能な限り最高のパフォーマンスを提供しませんが、ほとんどのアプリケーションでは十分です。そしてさらに重要なことに、それは間違いをすることをはるかに難しくします。
更新:SyncLockとキューを使用しないでください。代わりに、自動的にロックを処理するConcurrentQueueを使用してください。パフォーマンスが向上し、ミスをする可能性が低くなります。
実際には、貧弱な人のバージョンのThreadPoolを作成しました。 2番目のスレッドは、そこに座って何もせず、かなりの量の作業を行わなければ、作業を行わせることはできません。デリゲートをキューに入れて、スレッドがそれを取り出して実行する必要があります。
あなたの最善の策は、あなたが意図したことをして、.NET ThreadPoolを使用してそれを機能させることです。
おい、アルバハリの.Netスレッディング無料の電子ブックを読んでください。どうしてもつながっているので、これはプラグではありません。読んだり、同僚に読んだりして、何度も使ったことがあります。
プロデューサー/コンシューマークラスを作成することをお勧めします。このクラスでは、待機(ブロッキングなし)する単一のスレッドを開始し、タスクをそのキューにエンキューして、動作を開始するように通知できます。
ただググって。
GUIのいくつかのイベントは、バックグラウンドで実行されるいくつかの長期実行タスクを開始する必要があると想定しています。これを行うには、主に2つの方法があります。別のスレッドでメソッドを呼び出すだけの場合は、 同期メソッドを非同期に呼び出す で実行できます。私は通常次のようなことをします:
//delegate with same prototype as the method to call asynchrously
delegate void ProcessItemDelegate(object item);
//method to call asynchronously
private void ProcessItem(object item) { ... }
//method in the GUI thread
private void DoWork(object itemToProcess)
{
//create delegate to call asynchronously...
ProcessItemDelegate d = new ProcessItemDelegate(this.ProcessItem);
IAsyncResult result = d.BeginInvoke(itemToProcess,
new AsyncCallback(this.CallBackMethod),
d);
}
//method called when the async operation has completed
private void CallbackMethod(IAsyncResult ar)
{
ProcessItemDelegate d = (ProcessItemDelegate)ar.AsyncState;
//EndInvoke must be called on any delegate called asynchronously!
d.EndInvoke(ar);
}
このメソッドを使用するときは、コールバックがバックグラウンドスレッドで実行されるため、GUIの更新はInvokeを使用して行う必要があることに注意してください。
または、共有状態を使用してスレッド間で通信し、 EventWaitHandle を使用して共有状態の更新を通知することもできます。この例では、GUIのメソッドは、バックグラウンドで処理されるキューに作業項目を追加します。作業が利用可能になると、ワーカースレッドはキューのアイテムを処理します。
//shared state
private Queue workQueue;
private EventWaitHandle eventHandle;
//method running in gui thread
private void DoWork(Item itemToProcess)
{
//use a private lock object instead of lock...
lock(this.workQueue)
{
this.workQueue.Add(itemToProcess);
this.eventHandle.Set();
}
}
//method that runs on the background thread
private void QueueMonitor()
{
while(keepRunning)
{
//if the event handle is not signalled the processing thread will sleep here until it is signalled or the timeout expires
if(this.eventHandle.WaitOne(optionalTimeout))
{
lock(this.workQueue)
{
while(this.workQueue.Count > 0)
{
Item itemToProcess = this.workQueue.Dequeue();
//do something with item...
}
}
//reset wait handle - note that AutoResetEvent resets automatically
this.eventHandle.Reset();
}
}
}
Control.BeginInvoke()の利便性は、継承するのが困難です。する必要はありません。プロジェクトに新しいクラスを追加し、このコードを貼り付けます:
using System;
using System.Threading;
using System.Windows.Forms;
public partial class frmWorker : Form {
public frmWorker() {
// Start the worker thread
Thread t = new Thread(new ParameterizedThreadStart(WorkerThread));
t.IsBackground = true;
t.Start(this);
}
public void Stop() {
// Synchronous thread stop
this.Invoke(new MethodInvoker(stopWorker), null);
}
private void stopWorker() {
this.Close();
}
private static void WorkerThread(object frm) {
// Start the message loop
frmWorker f = frm as frmWorker;
f.CreateHandle();
Application.Run(f);
}
protected override void SetVisibleCore(bool value) {
// Shouldn't become visible
value = false;
base.SetVisibleCore(value);
}
}
これをテストするためのサンプルコードを次に示します。
public partial class Form1 : Form {
private frmWorker mWorker;
public Form1() {
InitializeComponent();
mWorker = new frmWorker();
}
private void button1_Click(object sender, EventArgs e) {
Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId);
mWorker.BeginInvoke(new MethodInvoker(RunThisOnThread));
}
private void RunThisOnThread() {
Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId);
}
private void button2_Click(object sender, EventArgs e) {
mWorker.Stop();
}
}
同期オブジェクトを使用して、新しいデータ(またはGUIの新しい状態)を処理する必要があることをスレッドに通知します。これを行う比較的簡単な方法の1つは、イベントオブジェクトを使用することです。これがどのように機能するかの概要です:
ほとんどの時間スリープする2番目のスレッドにループを配置しますが、[Interval]ごとに起動し、メソッドを実行するかどうかを通知する共有変数をチェックします。共有ブール値がtrueに設定されている場合は、実行しようとしているタスクを実行するメソッドを実行します...そのメソッドでは、メソッドに別の共有変数から必要なデータを収集させます。
メインGUIスレッドで、データをメソッドパラメーターの共有変数に入れ、ブールの「実行」共有変数をtrueに設定します...
ワーカーメソッドの内部では、完了したら共有bool "run"変数をfalseにリセットすることを忘れないでください。そうすることで、ループが発生しなくなります。同じインスタンスが繰り返し実行されることはありません...
イベントを使用するか、Grauenwolfが言ったように-メッセージキューを使用できます。各スレッドを管理シングルトンとしてラップし、そこから簡単に実装できます。貧乏人の公共施設でビットをめくることもできます。
また、状態マシンを実装し、メッセージを渡す代わりに、各スレッドがお互いを監視することもできます