39

フォーム内からオブジェクトのイベントをサブスクライブすると、基本的にコールバック メソッドの制御がイベント ソースに渡されます。そのイベント ソースが別のスレッドでイベントをトリガーすることを選択するかどうかはわかりません。

問題は、コールバックが呼び出されたときに、フォーム上でコントロールを更新できると想定できないことです。これは、フォームが実行されたスレッドとは異なるスレッドでイベント コールバックが呼び出された場合、これらのコントロールが例外をスローすることがあるためです。

4

6 に答える 6

35

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";
}
于 2008-08-08T18:05:24.493 に答える
18

重要な点は次のとおりです。

  1. 作成されたスレッド (フォームのスレッド) とは異なるスレッドから UI コントロールの呼び出しを行うことはできません。
  2. デリゲート呼び出し (つまり、イベント フック) は、イベントを発生させているオブジェクトと同じスレッドでトリガーされます。

したがって、別の「エンジン」スレッドが何らかの作業を行っていて、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";
}

本当に簡単です。

  1. InvokeRequiredを使用して、このコールバックが正しいスレッドで発生したかどうかを調べます。
  2. そうでない場合は、同じパラメーターを使用して正しいスレッドでコールバックを再度呼び出します。Invoke (ブロッキング) またはBeginInvoke (非ブロッキング) メソッドを使用して、メソッドを再度呼び出すことができます。
  3. 次に関数が呼び出されると、InvokeRequiredは false を返します。これは、現在正しいスレッドにあり、全員が満足しているためです。

これは、この問題に対処し、フォームをマルチスレッド イベント コールバックから保護する非常にコンパクトな方法です。

于 2008-08-08T17:35:40.937 に答える
9

このシナリオでは、匿名メソッドをよく使用します。

void SomethingHappened(object sender, EventArgs ea)
{
   MethodInvoker del = delegate{ textBox1.Text = "Something happened"; }; 
   InvokeRequired ? Invoke( del ) : del(); 
}
于 2008-08-31T21:35:33.897 に答える
2

として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.";
}
于 2012-05-30T21:57:58.433 に答える
2

このトピックには少し遅れましたが、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; }
        }
    }
}
于 2008-12-04T20:08:40.190 に答える
0

多くの単純なケースでは、MethodInvoker デリゲートを使用して、独自のデリゲート型を作成する必要をなくすことができます。

于 2008-08-08T17:41:18.333 に答える