81

.NET イベント モデルは、あるスレッドでイベントを発生させ、別のスレッドでそれをリッスンすることがよくあります。バックグラウンド スレッドから UI スレッドにイベントをマーシャリングする最もクリーンな方法は何かと考えていました。

コミュニティの提案に基づいて、私はこれを使用しました:

// earlier in the code
mCoolObject.CoolEvent+= 
           new CoolObjectEventHandler(mCoolObject_CoolEvent);
// then
private void mCoolObject_CoolEvent(object sender, CoolObjectEventArgs args)
{
    if (InvokeRequired)
    {
        CoolObjectEventHandler cb =
            new CoolObjectEventHandler(
                mCoolObject_CoolEvent);
        Invoke(cb, new object[] { sender, args });
        return;
    }
    // do the dirty work of my method here
}
4

10 に答える 10

45

このオンラインのコードがいくつかあります。他の提案よりもはるかに優れています。必ずチェックしてください。

使用例:

private void mCoolObject_CoolEvent(object sender, CoolObjectEventArgs args)
{
    // You could use "() =>" in place of "delegate"; it's a style choice.
    this.Invoke(delegate
    {
        // Do the dirty work of my method here.
    });
}
于 2008-11-03T11:30:56.523 に答える
28

いくつかの観察:

  • 2.0 より前の場合を除き、そのようなコードで単純なデリゲートを明示的に作成しないでください。
   BeginInvoke(new EventHandler<CoolObjectEventArgs>(mCoolObject_CoolEvent), 
               sender, 
               args);
  • また、args パラメーターは「params」型であるため、オブジェクト配列を作成して入力する必要はなく、リストを渡すだけで済みます。

  • 後者の場合、コードが非同期的に呼び出される結果になるため、おそらく優先Invokeするでしょう。何が起こるかというと、アプリが代わりに を取得することになります。BeginInvokeEndInvokeTargetInvocationException

于 2008-08-22T13:45:40.993 に答える
11

冗長なデリゲート宣言は避けます。

private void mCoolObject_CoolEvent(object sender, CoolObjectEventArgs args)
{
    if (InvokeRequired)
    {
        Invoke(new Action<object, CoolObjectEventArgs>(mCoolObject_CoolEvent), sender, args);
        return;
    }
    // do the dirty work of my method here
}

イベント以外の場合は、System.Windows.Forms.MethodInvokerデリゲートまたはを使用できますSystem.Action

編集:さらに、すべてのイベントには対応するEventHandlerデリゲートがあるため、デリゲートを再宣言する必要はまったくありません。

于 2008-08-22T13:42:47.477 に答える
6

私は自分の目的のために次の「ユニバーサル」クロス スレッド コール クラスを作成しましたが、共有する価値があると思います。

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;

namespace CrossThreadCalls
{
  public static class clsCrossThreadCalls
  {
    private delegate void SetAnyPropertyCallBack(Control c, string Property, object Value);
    public static void SetAnyProperty(Control c, string Property, object Value)
    {
      if (c.GetType().GetProperty(Property) != null)
      {
        //The given property exists
        if (c.InvokeRequired)
        {
          SetAnyPropertyCallBack d = new SetAnyPropertyCallBack(SetAnyProperty);
          c.BeginInvoke(d, c, Property, Value);
        }
        else
        {
          c.GetType().GetProperty(Property).SetValue(c, Value, null);
        }
      }
    }

    private delegate void SetTextPropertyCallBack(Control c, string Value);
    public static void SetTextProperty(Control c, string Value)
    {
      if (c.InvokeRequired)
      {
        SetTextPropertyCallBack d = new SetTextPropertyCallBack(SetTextProperty);
        c.BeginInvoke(d, c, Value);
      }
      else
      {
        c.Text = Value;
      }
    }
  }

また、別のスレッドから SetAnyProperty() を簡単に使用できます。

CrossThreadCalls.clsCrossThreadCalls.SetAnyProperty(lb_Speed, "Text", KvaserCanReader.GetSpeed.ToString());

この例では、上記の KvaserCanReader クラスが独自のスレッドを実行し、呼び出しを行ってメイン フォームの lb_Speed ラベルのテキスト プロパティを設定します。

于 2012-10-05T09:36:06.550 に答える
3

最もクリーンな方法は、間違いなくAOP ルートに進むことだと思います。いくつかの側面を作成し、必要な属性を追加すると、スレッド アフィニティを再度確認する必要がなくなります。

于 2009-04-29T17:33:31.610 に答える
3

Use the synchronisation context if you want to send a result to the UI thread. I needed to change the thread priority so I changed from using thread pool threads (commented out code) and created a new thread of my own. I was still able to use the synchronisation context to return whether the database cancel succeeded or not.

    #region SyncContextCancel

    private SynchronizationContext _syncContextCancel;

    /// <summary>
    /// Gets the synchronization context used for UI-related operations.
    /// </summary>
    /// <value>The synchronization context.</value>
    protected SynchronizationContext SyncContextCancel
    {
        get { return _syncContextCancel; }
    }

    #endregion //SyncContextCancel

    public void CancelCurrentDbCommand()
    {
        _syncContextCancel = SynchronizationContext.Current;

        //ThreadPool.QueueUserWorkItem(CancelWork, null);

        Thread worker = new Thread(new ThreadStart(CancelWork));
        worker.Priority = ThreadPriority.Highest;
        worker.Start();
    }

    SQLiteConnection _connection;
    private void CancelWork()//object state
    {
        bool success = false;

        try
        {
            if (_connection != null)
            {
                log.Debug("call cancel");
                _connection.Cancel();
                log.Debug("cancel complete");
                _connection.Close();
                log.Debug("close complete");
                success = true;
                log.Debug("long running query cancelled" + DateTime.Now.ToLongTimeString());
            }
        }
        catch (Exception ex)
        {
            log.Error(ex.Message, ex);
        }

        SyncContextCancel.Send(CancelCompleted, new object[] { success });
    }

    public void CancelCompleted(object state)
    {
        object[] args = (object[])state;
        bool success = (bool)args[0];

        if (success)
        {
            log.Debug("long running query cancelled" + DateTime.Now.ToLongTimeString());

        }
    }
于 2015-07-07T08:20:11.820 に答える
2

呼び出しが必要であると常に想定するのはどれほどコストがかかるのだろうといつも思っていました...

private void OnCoolEvent(CoolObjectEventArgs e)
{
  BeginInvoke((o,e) => /*do work here*/,this, e);
}
于 2008-08-22T13:44:28.267 に答える
2

興味深い点として、WPF のバインディングはマーシャリングを自動的に処理するため、特別なことをしなくても、バックグラウンド スレッドで変更されるオブジェクト プロパティに UI をバインドできます。これは、私にとって大きな時間の節約になることが証明されています。

XAML の場合:

<TextBox Text="{Binding Path=Name}"/>
于 2008-08-22T14:20:36.213 に答える
0

SynchronizationContextを入力として受け入れ、それを使用してイベントを呼び出す、ある種の汎用コンポーネントの開発を試みることができます。

于 2008-08-22T13:53:17.420 に答える