c# chart - メインのUIスレッドで.NETでイベントを発生させる




invoke label (8)

私はMike Boukの答え(+1)が大好きだったので、私はコードベースに取り入れました。 私は、彼のDynamicInvoke呼び出しが、それが呼び出す代理人が不一致のパラメータのためにEventHandler代理人でない場合、実行時例外をスローすることを心配しています。 あなたはバックグラウンドスレッドにいるので、UIメソッドを非同期に呼び出すことができ、それが終了したかどうかは関係ありません。

以下の私のバージョンは、EventHandlerデリゲートでのみ使用でき、その呼び出しリスト内の他のデリゲートは無視されます。 EventHandlerデリゲートは何も返さないため、結果は必要ありません。 これにより、BeginInvoke呼び出しでEventHandlerを渡すことによって、非同期プロセスが完了した後にEndInvokeを呼び出すことができます。 この呼び出しは、AsynchronousCallbackを介してIAsyncResult.AsyncStateでこのEventHandlerを返します。この時点で、EventHandler.EndInvokeが呼び出されます。

/// <summary>
/// Safely raises any EventHandler event asynchronously.
/// </summary>
/// <param name="sender">The object raising the event (usually this).</param>
/// <param name="e">The EventArgs for this event.</param>
public static void Raise(this MulticastDelegate thisEvent, object sender, 
    EventArgs e)
{
  EventHandler uiMethod; 
  ISynchronizeInvoke target; 
  AsyncCallback callback = new AsyncCallback(EndAsynchronousEvent);

  foreach (Delegate d in thisEvent.GetInvocationList())
  {
    uiMethod = d as EventHandler;
    if (uiMethod != null)
    {
      target = d.Target as ISynchronizeInvoke; 
      if (target != null) target.BeginInvoke(uiMethod, new[] { sender, e }); 
      else uiMethod.BeginInvoke(sender, e, callback, uiMethod);
    }
  }
}

private static void EndAsynchronousEvent(IAsyncResult result) 
{ 
  ((EventHandler)result.AsyncState).EndInvoke(result); 
}

そして使用法:

MyEventHandlerEvent.Raise(this, MyEventArgs);

私は.NETで他の開発者が最終的に消費するクラスライブラリを開発しています。 このライブラリはいくつかのワーカースレッドを使用し、 それらのスレッドはWinForms / WPFアプリケーションでいくつかのUIコントロールを更新するステータスイベントを発生させます。

通常、更新ごとに、WinFormsまたは同等のWPFプロパティの.InvokeRequiredプロパティをチェックし、更新のためにメインのUIスレッドでこれを呼び出す必要があります。 これはすぐに古くなる可能性があり、最終的な開発者がこれを行うことについて何か気にしません。だから...

私のライブラリがメインのUIスレッドからイベント/デリゲートを起動/起動できる方法はありますか?

特に...

  1. 私は自動的に使用する "メイン"スレッドを "検出"する必要がありますか?
  2. もしそうでなければ、エンド・デベロッパーにアプリケーション起動時に(擬似的な) UseThisThreadForEvents()メソッドを呼び出さなければならないので、その呼び出しからターゲット・スレッドを取得できますか?


EventHandlerが常に動作するわけではなく、ISynchronizeInvokeがWPFで動作しないというメソッドに頼っていることがわかりました。 私の試みは、このように見える、それは誰かを助けるかもしれない:

public static class Extensions
{
    // Extension method which marshals events back onto the main thread
    public static void Raise(this MulticastDelegate multicast, object sender, EventArgs args)
    {
        foreach (Delegate del in multicast.GetInvocationList())
        {
            // Try for WPF first
            DispatcherObject dispatcherTarget = del.Target as DispatcherObject;
            if (dispatcherTarget != null && !dispatcherTarget.Dispatcher.CheckAccess())
            {
                // WPF target which requires marshaling
                dispatcherTarget.Dispatcher.BeginInvoke(del, sender, args);
            }
            else
            {
                // Maybe its WinForms?
                ISynchronizeInvoke syncTarget = del.Target as ISynchronizeInvoke;
                if (syncTarget != null && syncTarget.InvokeRequired)
                {
                    // WinForms target which requires marshaling
                    syncTarget.BeginInvoke(del, new object[] { sender, args });
                }
                else
                {
                    // Just do it.
                    del.DynamicInvoke(sender, args);
                }
            }
        }
    }
    // Extension method which marshals actions back onto the main thread
    public static void Raise<T>(this Action<T> action, T args)
    {
        // Try for WPF first
        DispatcherObject dispatcherTarget = action.Target as DispatcherObject;
        if (dispatcherTarget != null && !dispatcherTarget.Dispatcher.CheckAccess())
        {
            // WPF target which requires marshaling
            dispatcherTarget.Dispatcher.BeginInvoke(action, args);
        }
        else
        {
            // Maybe its WinForms?
            ISynchronizeInvoke syncTarget = action.Target as ISynchronizeInvoke;
            if (syncTarget != null && syncTarget.InvokeRequired)
            {
                // WinForms target which requires marshaling
                syncTarget.BeginInvoke(action, new object[] { args });
            }
            else
            {
                // Just do it.
                action.DynamicInvoke(args);
            }
        }
    }
}

あなたのライブラリは、イベントの呼び出しリスト内の各デリゲートのターゲットをチェックし、ターゲットがISynchronizeInvokeの場合、ターゲットスレッドへの呼び出しをマーシャリングできます。

private void RaiseEventOnUIThread(Delegate theEvent, object[] args)
{
  foreach (Delegate d in theEvent.GetInvocationList())
  {
    ISynchronizeInvoke syncer = d.Target as ISynchronizeInvoke;
    if (syncer == null)
    {
      d.DynamicInvoke(args);
    }
    else
    {
      syncer.BeginInvoke(d, args);  // cleanup omitted
    }
  }
}

スレッディング契約をより明示的にするもう1つのアプローチは、ライブラリのクライアントに、イベントを発生させたいスレッドのISynchronizeInvokeまたはSynchronizationContextを渡すよう要求することです。 これにより、図書館のユーザーは、「委任先のターゲットを密かにチェックする」アプローチよりも、可視性と制御性が少し向上します。

あなたの2番目の質問に関しては、OnXxxの中にスレッドをマーシャリングするか、ユーザーコードが呼び出すAPIを呼び出すことで、イベントが発生する可能性があります。


ここではitwolsonのアイデアは、私のために働いている拡張メソッドとして表現されます:

/// <summary>Extension methods for EventHandler-type delegates.</summary>
public static class EventExtensions
{
    /// <summary>Raises the event (on the UI thread if available).</summary>
    /// <param name="multicastDelegate">The event to raise.</param>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">An EventArgs that contains the event data.</param>
    /// <returns>The return value of the event invocation or null if none.</returns>
    public static object Raise(this MulticastDelegate multicastDelegate, object sender, EventArgs e)
    {
        object retVal = null;

        MulticastDelegate threadSafeMulticastDelegate = multicastDelegate;
        if (threadSafeMulticastDelegate != null)
        {
            foreach (Delegate d in threadSafeMulticastDelegate.GetInvocationList())
            {
                var synchronizeInvoke = d.Target as ISynchronizeInvoke;
                if ((synchronizeInvoke != null) && synchronizeInvoke.InvokeRequired)
                {
                    retVal = synchronizeInvoke.EndInvoke(synchronizeInvoke.BeginInvoke(d, new[] { sender, e }));
                }
                else
                {
                    retVal = d.DynamicInvoke(new[] { sender, e });
                }
            }
        }

        return retVal;
    }
}

次のようにイベントを呼び出すだけです。

MyEvent.Raise(this, EventArgs.Empty);

私はこれらの答えと例が気に入っていますが、標準的にはライブラリをすべて間違って書いています。 あなたのイベントを他のスレッドのためにマーシャリングしないことが重要です。 あなたの行事は、どこにいるのか解雇されている所で処理してください。 そのイベントがスレッドを変更するときが来たら、その時点でエンド開発者にそのことをさせることが重要です。


私はこれが古いスレッドだと知っていますが、それが本当に似たようなものを作るのを手伝ってくれたので、コードを共有したいと思っています。 新しいC#7機能を使用して、スレッド対応Raise関数を作成できました。 EventHandlerデリゲートテンプレートとC#7のパターンマッチング、およびLINQを使用してフィルタリングと型を設定します。

public static void ThreadAwareRaise<TEventArgs>(this EventHandler<TEventArgs> customEvent,
    object sender, TEventArgs e) where TEventArgs : EventArgs
{
    foreach (var d in customEvent.GetInvocationList().OfType<EventHandler<TEventArgs>>())
        switch (d.Target)
        {
            case DispatcherObject dispatchTartget:
                dispatchTartget.Dispatcher.BeginInvoke(d, sender, e);
                break;
            case ISynchronizeInvoke syncTarget when syncTarget.InvokeRequired:
                syncTarget.BeginInvoke(d, new[] {sender, e});
                break;
            default:
                d.Invoke(sender, e);
                break;
        }
}

.NET 2.0の場合、ここでは、私が書いた素晴らしいコードがあります。これは、あなたが望むものを正確に行い、 Control任意のプロパティに対して機能します。

private delegate void SetControlPropertyThreadSafeDelegate(
    Control control, 
    string propertyName, 
    object propertyValue);

public static void SetControlPropertyThreadSafe(
    Control control, 
    string propertyName, 
    object propertyValue)
{
  if (control.InvokeRequired)
  {
    control.Invoke(new SetControlPropertyThreadSafeDelegate               
    (SetControlPropertyThreadSafe), 
    new object[] { control, propertyName, propertyValue });
  }
  else
  {
    control.GetType().InvokeMember(
        propertyName, 
        BindingFlags.SetProperty, 
        null, 
        control, 
        new object[] { propertyValue });
  }
}

これを次のように呼んでください:

// thread-safe equivalent of
// myLabel.Text = status;
SetControlPropertyThreadSafe(myLabel, "Text", status);

.NET 3.0以上を使用している場合、上記のメソッドをControlクラスの拡張メソッドとして書き直すと、次の呼び出しを簡素化できます。

myLabel.SetPropertyThreadSafe("Text", status);

更新05/10/2010:

.NET 3.0では、次のコードを使用する必要があります。

private delegate void SetPropertyThreadSafeDelegate<TResult>(
    Control @this, 
    Expression<Func<TResult>> property, 
    TResult value);

public static void SetPropertyThreadSafe<TResult>(
    this Control @this, 
    Expression<Func<TResult>> property, 
    TResult value)
{
  var propertyInfo = (property.Body as MemberExpression).Member 
      as PropertyInfo;

  if (propertyInfo == null ||
      [email protected]().IsSubclassOf(propertyInfo.ReflectedType) ||
      @this.GetType().GetProperty(
          propertyInfo.Name, 
          propertyInfo.PropertyType) == null)
  {
    throw new ArgumentException("The lambda expression 'property' must reference a valid property on this Control.");
  }

  if (@this.InvokeRequired)
  {
      @this.Invoke(new SetPropertyThreadSafeDelegate<TResult> 
      (SetPropertyThreadSafe), 
      new object[] { @this, property, value });
  }
  else
  {
      @this.GetType().InvokeMember(
          propertyInfo.Name, 
          BindingFlags.SetProperty, 
          null, 
          @this, 
          new object[] { value });
  }
}

これはLINQとラムダ式を使用して、よりクリーンでシンプルで安全な構文を可能にします。

myLabel.SetPropertyThreadSafe(() => myLabel.Text, status); // status has to be a string or this will fail to compile

プロパティ名はコンパイル時にチェックされるだけでなく、プロパティの型も同様です。したがって、(たとえば)ブール値プロパティに文字列値を代入することは不可能であり、実行時例外が発生します。

残念ながら、これは他のControlのプロパティと値を渡すなど、誰かが愚かなことをやめないようにするため、次のようにうまくコンパイルします。

myLabel.SetPropertyThreadSafe(() => aForm.ShowIcon, false);

そこで、渡されたプロパティがメソッドが呼び出されているControlに実際に属していることを確認するためにランタイムチェックを追加しました。 完璧ではありませんが、.NET 2.0よりはるかに優れています。

コンパイル時の安全性のためにこのコードを改善する方法について他に誰かが提案したら、コメントしてください!





c# multithreading design-patterns