フォーム内からオブジェクトのイベントをサブスクライブすると、基本的にコールバック メソッドの制御がイベント ソースに渡されます。そのイベント ソースが別のスレッドでイベントをトリガーすることを選択するかどうかはわかりません。
問題は、コールバックが呼び出されたときに、フォーム上でコントロールを更新できると想定できないことです。これは、フォームが実行されたスレッドとは異なるスレッドでイベント コールバックが呼び出された場合、これらのコントロールが例外をスローすることがあるためです。
フォーム内からオブジェクトのイベントをサブスクライブすると、基本的にコールバック メソッドの制御がイベント ソースに渡されます。そのイベント ソースが別のスレッドでイベントをトリガーすることを選択するかどうかはわかりません。
問題は、コールバックが呼び出されたときに、フォーム上でコントロールを更新できると想定できないことです。これは、フォームが実行されたスレッドとは異なるスレッドでイベント コールバックが呼び出された場合、これらのコントロールが例外をスローすることがあるためです。
Simon のコードを少し単純化するために、組み込みの汎用 Action デリゲートを使用できます。実際には必要のない一連のデリゲート型をコードに追加する必要がなくなります。また、.NET 3.5 では、params パラメーターが Invoke メソッドに追加されたため、一時配列を定義する必要はありません。
void SomethingHappened(object sender, EventArgs ea)
{
if (InvokeRequired)
{
Invoke(new Action<object, EventArgs>(SomethingHappened), sender, ea);
return;
}
textBox1.Text = "Something happened";
}
重要な点は次のとおりです。
したがって、別の「エンジン」スレッドが何らかの作業を行っていて、UI に反映される可能性のある状態の変化 (進行状況バーなど) を監視する UI がある場合、問題が発生します。エンジンの発火は、フォームによってフックされたオブジェクト変更イベントです。しかし、フォームがエンジンに登録されたコールバック デリゲートは、フォームのスレッドではなく、エンジンのスレッドで呼び出されます。したがって、そのコールバックからコントロールを更新することはできません。どっ!
BeginInvokeが役に立ちます。すべてのコールバック メソッドでこの単純なコーディング モデルを使用するだけで、問題なく動作することを確認できます。
private delegate void EventArgsDelegate(object sender, EventArgs ea);
void SomethingHappened(object sender, EventArgs ea)
{
//
// Make sure this callback is on the correct thread
//
if (this.InvokeRequired)
{
this.Invoke(new EventArgsDelegate(SomethingHappened), new object[] { sender, ea });
return;
}
//
// Do something with the event such as update a control
//
textBox1.Text = "Something happened";
}
本当に簡単です。
これは、この問題に対処し、フォームをマルチスレッド イベント コールバックから保護する非常にコンパクトな方法です。
このシナリオでは、匿名メソッドをよく使用します。
void SomethingHappened(object sender, EventArgs ea)
{
MethodInvoker del = delegate{ textBox1.Text = "Something happened"; };
InvokeRequired ? Invoke( del ) : del();
}
としてlazy programmer
、私はこれを行うための非常に怠惰な方法を持っています。
私がやっていることは、これだけです。
private void DoInvoke(MethodInvoker del) {
if (InvokeRequired) {
Invoke(del);
} else {
del();
}
}
//example of how to call it
private void tUpdateLabel(ToolStripStatusLabel lbl, String val) {
DoInvoke(delegate { lbl.Text = val; });
}
関数内に DoInvoke をインライン化するか、別の関数内に非表示にして、汚い作業を行うことができます。
関数を直接 DoInvoke メソッドに渡すことができることに注意してください。
private void directPass() {
DoInvoke(this.directInvoke);
}
private void directInvoke() {
textLabel.Text = "Directly passed.";
}
このトピックには少し遅れましたが、Event-Based Asynchronous Pattern をご覧になることをお勧めします。適切に実装すると、イベントが常に UI スレッドから発生することが保証されます。
1 つの同時呼び出しのみを許可する簡単な例を次に示します。複数の呼び出し/イベントをサポートするには、もう少し配管が必要です。
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public class MainForm : Form
{
private TypeWithAsync _type;
[STAThread()]
public static void Main()
{
Application.EnableVisualStyles();
Application.Run(new MainForm());
}
public MainForm()
{
_type = new TypeWithAsync();
_type.DoSomethingCompleted += DoSomethingCompleted;
var panel = new FlowLayoutPanel() { Dock = DockStyle.Fill };
var btn = new Button() { Text = "Synchronous" };
btn.Click += SyncClick;
panel.Controls.Add(btn);
btn = new Button { Text = "Asynchronous" };
btn.Click += AsyncClick;
panel.Controls.Add(btn);
Controls.Add(panel);
}
private void SyncClick(object sender, EventArgs e)
{
int value = _type.DoSomething();
MessageBox.Show(string.Format("DoSomething() returned {0}.", value));
}
private void AsyncClick(object sender, EventArgs e)
{
_type.DoSomethingAsync();
}
private void DoSomethingCompleted(object sender, DoSomethingCompletedEventArgs e)
{
MessageBox.Show(string.Format("DoSomethingAsync() returned {0}.", e.Value));
}
}
class TypeWithAsync
{
private AsyncOperation _operation;
// synchronous version of method
public int DoSomething()
{
Thread.Sleep(5000);
return 27;
}
// async version of method
public void DoSomethingAsync()
{
if (_operation != null)
{
throw new InvalidOperationException("An async operation is already running.");
}
_operation = AsyncOperationManager.CreateOperation(null);
ThreadPool.QueueUserWorkItem(DoSomethingAsyncCore);
}
// wrapper used by async method to call sync version of method, matches WaitCallback so it
// can be queued by the thread pool
private void DoSomethingAsyncCore(object state)
{
int returnValue = DoSomething();
var e = new DoSomethingCompletedEventArgs(returnValue);
_operation.PostOperationCompleted(RaiseDoSomethingCompleted, e);
}
// wrapper used so async method can raise the event; matches SendOrPostCallback
private void RaiseDoSomethingCompleted(object args)
{
OnDoSomethingCompleted((DoSomethingCompletedEventArgs)args);
}
private void OnDoSomethingCompleted(DoSomethingCompletedEventArgs e)
{
var handler = DoSomethingCompleted;
if (handler != null) { handler(this, e); }
}
public EventHandler<DoSomethingCompletedEventArgs> DoSomethingCompleted;
}
public class DoSomethingCompletedEventArgs : EventArgs
{
private int _value;
public DoSomethingCompletedEventArgs(int value)
: base()
{
_value = value;
}
public int Value
{
get { return _value; }
}
}
}
多くの単純なケースでは、MethodInvoker デリゲートを使用して、独自のデリゲート型を作成する必要をなくすことができます。