web-dev-qa-db-ja.com

別のスレッドでイベントを発生させる

ライブフィードを処理し、データをリスナーに非常に高速な方法でブロードキャストする必要があるコンポーネントを開発しています(約100ナノ秒のレベルの精度で、それが可能な場合はそれよりも低くなります)。現在、サブスクライバーがサブスクライブできるコード。ただし、C#では、イベントハンドラーがイベントを発生させる同じスレッドで実行されるため、すべてのサブスクライバーがイベントの処理を完了するまで、イベントを発生させる私のスレッドはブロックされます。私はサブスクライバーのコードを制御できません。そのため、サブスクライバーは、イベントハンドラーで時間のかかる操作を実行でき、ブロードキャストしているスレッドをブロックする可能性があります。

他のサブスクライバーにデータをブロードキャストできるが、非常に高速にデータをブロードキャストできるようにするにはどうすればよいですか?

22
Hitesh P

100 nsは、攻撃するのが非常に難しいターゲットです。あなたが何をしているのか、なぜそのようなパフォーマンスを発揮するのかを深く理解することが必要だと思います。

ただし、イベントサブスクライバーを非同期に呼び出すことは、かなり簡単に解決できます。それはすでに答えられています ここ 他の誰がジョン・スキートによって。

foreach (MyDelegate action in multicast.GetInvocationList())
{
    action.BeginInvoke(...);
}

edit:また、 リアルタイムオペレーティングシステム で実行する必要があることも言及しておく必要があります。ユーザー。

10
dss539

タスクを探しているようです。以下は、私が自分のジョブ用に作成した拡張メソッドで、非同期にイベントを呼び出して、すべてのイベントハンドラーが独自のスレッド上にあるようにすることができます。それが私にとって必要条件ではなかったので、その速度についてコメントすることはできません。


更新

コメントに基づいて、すべてのサブスクライバーを呼び出すタスクが1つだけ作成されるように調整しました

/// <summary>
/// Extension method to safely encapsulate asynchronous event calls with checks
/// </summary>
/// <param name="evnt">The event to call</param>
/// <param name="sender">The sender of the event</param>
/// <param name="args">The arguments for the event</param>
/// <param name="object">The state information that is passed to the callback method</param>
/// <remarks>
/// This method safely calls the each event handler attached to the event. This method uses <see cref="System.Threading.Tasks"/> to
/// asynchronously call invoke without any exception handling. As such, if any of the event handlers throw exceptions the application will
/// most likely crash when the task is collected. This is an explicit decision since it is really in the hands of the event handler
/// creators to make sure they handle issues that occur do to their code. There isn't really a way for the event raiser to know
/// what is going on.
/// </remarks>
[System.Diagnostics.DebuggerStepThrough]
public static void AsyncSafeInvoke( this EventHandler evnt, object sender, EventArgs args )
{
    // Used to make a temporary copy of the event to avoid possibility of
    // a race condition if the last subscriber unsubscribes
    // immediately after the null check and before the event is raised.
    EventHandler handler = evnt;
    if (handler != null)
    {
        // Manually calling all event handlers so that we could capture and aggregate all the
        // exceptions that are thrown by any of the event handlers attached to this event.  
        var invocationList = handler.GetInvocationList();

        Task.Factory.StartNew(() =>
        {
            foreach (EventHandler h in invocationList)
            {
                // Explicitly not catching any exceptions. While there are several possibilities for handling these 
                // exceptions, such as a callback, the correct place to handle the exception is in the event handler.
                h.Invoke(sender, args);
            }
        });
    }
}
5
Iddillian

イベントハンドラーでこれらの単純な拡張メソッドを使用できます。

_public static void Raise<T>(this EventHandler<T> handler, object sender, T e) where T : EventArgs {
    if (handler != null) handler(sender, e);
}

public static void Raise(this EventHandler handler, object sender, EventArgs e) {
    if (handler != null) handler(sender, e);
}

public static void RaiseOnDifferentThread<T>(this EventHandler<T> handler, object sender, T e) where T : EventArgs {
    if (handler != null) Task.Factory.StartNewOnDifferentThread(() => handler.Raise(sender, e));
}

public static void RaiseOnDifferentThread(this EventHandler handler, object sender, EventArgs e) {
    if (handler != null) Task.Factory.StartNewOnDifferentThread(() => handler.Raise(sender, e));
}

public static Task StartNewOnDifferentThread(this TaskFactory taskFactory, Action action) {
    return taskFactory.StartNew(action: action, cancellationToken: new CancellationToken());
}
_

使用法:

_public static Test() {
     myEventHandler.RaiseOnDifferentThread(null, EventArgs.Empty);
}
_

説明されているように、cancellationTokenは、StartNew()が実際に別のスレッドを使用することを保証するために必要です here

4
Erwin Mayer

これが100nsの要件を確実に満たすかどうかは話せませんが、ここでは、エンドユーザーに、入力するConcurrentQueueを提供する方法を提供し、別のスレッドでリッスンできる代替案を示します。

class Program
{
    static void Main(string[] args)
    {
        var multicaster = new QueueMulticaster<int>();

        var listener1 = new Listener(); //Make a couple of listening Q objects. 
        listener1.Listen();
        multicaster.Subscribe(listener1);

        var listener2 = new Listener();
        listener2.Listen();
        multicaster.Subscribe(listener2);

        multicaster.Broadcast(6); //Send a 6 to both concurrent Queues. 
        Console.ReadLine();
    }
}

//The listeners would run on their own thread and poll the Q like crazy. 
class Listener : IListenToStuff<int>
{
    public ConcurrentQueue<int> StuffQueue { get; set; }

    public void Listen()
    {
        StuffQueue = new ConcurrentQueue<int>();
        var t = new Thread(ListenAggressively);
        t.Start();

    }

    void ListenAggressively()
    {
        while (true)
        {
            int val;
            if(StuffQueue.TryDequeue(out val))
                Console.WriteLine(val);
        }
    }
}

//Simple class that allows you to subscribe a Queue to a broadcast event. 
public class QueueMulticaster<T>
{
    readonly List<IListenToStuff<T>> _subscribers = new List<IListenToStuff<T>>();
    public void Subscribe(IListenToStuff<T> subscriber)
    {
        _subscribers.Add(subscriber);
    }
    public void Broadcast(T value)
    {
        foreach (var listenToStuff in _subscribers)
        {
            listenToStuff.StuffQueue.Enqueue(value);
        }
    }
}

public interface IListenToStuff<T>
{
    ConcurrentQueue<T> StuffQueue { get; set; }
}

他のリスナーでの処理を保持できないという事実を考えると、これは複数のスレッドを意味します。リスナーに専用のリスニングスレッドを用意することは、試してみるのに妥当なアプローチのように思われ、同時実行キューは適切な配信メカニズムのように見えます。この実装では、常にポーリングしているだけですが、AutoResetEventのようなものを使用して、スレッドシグナリングを使用してCPU負荷を減らすことができます。

2
deepee1

信号と共有メモリは非常に高速です。別の信号を送信して、共有メモリの場所からメッセージを読み取るようにアプリケーションに指示できます。もちろん、信号は依然として、低レイテンシが必要な場合に、優先度の高いスレッドでアプリケーションが消費する必要があるイベントです。データにタイムタグを含めて、受信機が避けられない待ち時間を補償できるようにします。

0
user191880