セカンダリスレッドで操作の連続ループを実行するVisual C#プログラムを書いています。ときどき、そのスレッドがタスクを終了したときに、イベントハンドラーをトリガーする必要があります。私のプログラムはそれを行いますが、イベントハンドラーがトリガーされると、セカンダリスレッドはイベントハンドラーが終了するまで待機してからスレッドを続行します。どうすれば継続できますか?これが私が現在それを構造化している方法です...
_class TestClass
{
private Thread SecondaryThread;
public event EventHandler OperationFinished;
public void StartMethod()
{
...
SecondaryThread.Start(); //start the secondary thread
}
private void SecondaryThreadMethod()
{
...
OperationFinished(null, new EventArgs());
... //This is where the program waits for whatever operations take
//place when OperationFinished is triggered.
}
}
_
このコードは私のデバイスの1つのAPIの一部です。 OperationFinishedイベントがトリガーされたとき、クライアントアプリケーションが、API操作を失敗させることなく、必要なすべてのことを実行できるように(つまり、それに応じてGUIを更新できるように)します。
また、イベントハンドラーにパラメーターを渡したくない場合は、OperationFinished(null, new EventArgs())
を使用して構文を修正できますか?
それで、リスナーがバックグラウンドスレッドをブロックしないようにイベントを発生させたいですか? 2、3分で例を示します。とてもシンプルです:-)
ここに行きます:最初の重要な注意!BeginInvoke
を呼び出すときはいつでも、対応するEndInvoke
を呼び出す必要があります。そうでない場合、呼び出されたメソッドが例外をスローした場合orが値を返した場合、ThreadPoolスレッドは解放されてプールに戻されないため、スレッドリークが発生します。
class TestHarness
{
static void Main(string[] args)
{
var raiser = new SomeClass();
// Emulate some event listeners
raiser.SomeEvent += (sender, e) => { Console.WriteLine(" Received event"); };
raiser.SomeEvent += (sender, e) =>
{
// Bad listener!
Console.WriteLine(" Blocking event");
System.Threading.Thread.Sleep(5000);
Console.WriteLine(" Finished blocking event");
};
// Listener who throws an exception
raiser.SomeEvent += (sender, e) =>
{
Console.WriteLine(" Received event, time to die!");
throw new Exception();
};
// Raise the event, see the effects
raiser.DoSomething();
Console.ReadLine();
}
}
class SomeClass
{
public event EventHandler SomeEvent;
public void DoSomething()
{
OnSomeEvent();
}
private void OnSomeEvent()
{
if (SomeEvent != null)
{
var eventListeners = SomeEvent.GetInvocationList();
Console.WriteLine("Raising Event");
for (int index = 0; index < eventListeners.Count(); index++)
{
var methodToInvoke = (EventHandler)eventListeners[index];
methodToInvoke.BeginInvoke(this, EventArgs.Empty, EndAsyncEvent, null);
}
Console.WriteLine("Done Raising Event");
}
}
private void EndAsyncEvent(IAsyncResult iar)
{
var ar = (System.Runtime.Remoting.Messaging.AsyncResult)iar;
var invokedMethod = (EventHandler)ar.AsyncDelegate;
try
{
invokedMethod.EndInvoke(iar);
}
catch
{
// Handle any exceptions that were thrown by the invoked method
Console.WriteLine("An event listener went kaboom!");
}
}
}
Task Parallel Library を使用すると、次のことが可能になります。
Task.Factory.FromAsync( ( asyncCallback, @object ) => this.OperationFinished.BeginInvoke( this, EventArgs.Empty, asyncCallback, @object ), this.OperationFinished.EndInvoke, null );
また、イベントハンドラーにパラメーターを渡したくない場合は、OperationFinished(null、new EventArgs())を使用して構文を修正できますか?
いいえ。通常、次のように呼び出します。
OperationFinished(this, EventArgs.Empty);
オブジェクトは常に送信者として渡す必要があります-これはパターンで期待されます(通常は無視されます)。 EventArgs.Emptyは、新しいEventArgs()よりも優れています。
これを別のスレッドで起動するには、スレッドプールを使用するのがおそらく最も簡単な方法です。
private void RaiseOperationFinished()
{
ThreadPool.QueueUserWorkItem( new WaitCallback( (s) =>
{
if (this.OperationFinished != null)
this.OperationFinished(this, EventArgs.Empty);
}));
}
つまり、別のスレッドでイベントを発生させることは、予期しない動作を引き起こす可能性があるため、徹底的に文書化する必要があります。
イベントデリゲートでBeginInvokeメソッドとEndInvokeメソッドを試してください。これらはすぐに返され、ポーリング、待機ハンドル、またはコールバック関数を使用して、メソッドが完了したことを通知できます。概要については こちら を参照してください。あなたの例では、イベントはあなたが使用するデリゲートです
多分以下のMethod2またはMethod3が役立ちます:)
public partial class Form1 : Form
{
private Thread SecondaryThread;
public Form1()
{
InitializeComponent();
OperationFinished += callback1;
OperationFinished += callback2;
OperationFinished += callback3;
}
private void Form1_Load(object sender, EventArgs e)
{
SecondaryThread = new Thread(new ThreadStart(SecondaryThreadMethod));
SecondaryThread.Start();
}
private void SecondaryThreadMethod()
{
Stopwatch sw = new Stopwatch();
sw.Restart();
OnOperationFinished(new MessageEventArg("test1"));
OnOperationFinished(new MessageEventArg("test2"));
OnOperationFinished(new MessageEventArg("test3"));
//This is where the program waits for whatever operations take
//place when OperationFinished is triggered.
sw.Stop();
Invoke((MethodInvoker)delegate
{
richTextBox1.Text += "Time taken (ms): " + sw.ElapsedMilliseconds + "\n";
});
}
void callback1(object sender, MessageEventArg e)
{
Thread.Sleep(2000);
Invoke((MethodInvoker)delegate
{
richTextBox1.Text += e.Message + "\n";
});
}
void callback2(object sender, MessageEventArg e)
{
Thread.Sleep(2000);
Invoke((MethodInvoker)delegate
{
richTextBox1.Text += e.Message + "\n";
});
}
void callback3(object sender, MessageEventArg e)
{
Thread.Sleep(2000);
Invoke((MethodInvoker)delegate
{
richTextBox1.Text += e.Message + "\n";
});
}
public event EventHandler<MessageEventArg> OperationFinished;
protected void OnOperationFinished(MessageEventArg e)
{
//##### Method1 - Event raised on the same thread #####
//EventHandler<MessageEventArg> handler = OperationFinished;
//if (handler != null)
//{
// handler(this, e);
//}
//##### Method2 - Event raised on (the same) separate thread for all listener #####
//EventHandler<MessageEventArg> handler = OperationFinished;
//if (handler != null)
//{
// Task.Factory.StartNew(() => handler(this, e));
//}
//##### Method3 - Event raised on different threads for each listener #####
if (OperationFinished != null)
{
foreach (EventHandler<MessageEventArg> handler in OperationFinished.GetInvocationList())
{
Task.Factory.FromAsync((asyncCallback, @object) => handler.BeginInvoke(this, e, asyncCallback, @object), handler.EndInvoke, null);
}
}
}
}
public class MessageEventArg : EventArgs
{
public string Message { get; set; }
public MessageEventArg(string message)
{
this.Message = message;
}
}
}
私は、UIを更新するデリゲートとして子スレッドに渡すメソッドを定義することを好みます。最初にデリゲートを定義します。
public delegate void ChildCallBackDelegate();
子スレッドでデリゲートメンバーを定義します。
public ChildCallbackDelegate ChildCallback {get; set;}
呼び出しクラスで、UIを更新するメソッドを定義します。別のスレッドから呼び出されているため、ターゲットコントロールのディスパッチャーでラップする必要があります。 BeginInvokeに注意してください。このコンテキストでは、EndInvokeは必要ありません。
private void ChildThreadUpdater()
{
yourControl.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Background
, new System.Threading.ThreadStart(delegate
{
// update your control here
}
));
}
子スレッドを起動する前に、そのChildCallBackプロパティを設定します。
theChild.ChildCallBack = new ChildCallbackDelegate(ChildThreadUpdater);
次に、子スレッドが親を更新したい場合:
ChildCallBack();
BackgroundWorker クラスを見てください。私はそれがまさにあなたが求めていることをしていると思います。
編集:私があなたが求めていると思うのは、バックグラウンドタスク全体のごく一部のみが完了したときにイベントを起動する方法です。 BackgroundWorkerは、「ProgressChanged」と呼ばれるイベントを提供します。これにより、プロセス全体の一部が完了したことをメインスレッドに報告できます。次に、すべての非同期作業が完了すると、「RunWorkerCompleted」イベントが発生します。