Je développe un composant qui doit traiter le flux en direct et diffuser les données aux auditeurs de manière assez rapide (avec une précision d'environ 100 nano secondes, encore moins que si je peux le faire). Actuellement, je déclenche un événement de mon code auquel l'abonné peut s'abonner. Cependant, car dans les gestionnaires d'événements C # exécutés sur le même thread qui déclenche l'événement, mon thread qui déclenche l'événement sera bloqué jusqu'à ce que tous les abonnés aient fini de traiter l'événement. Je n'ai aucun contrôle sur le code des abonnés, ils peuvent donc éventuellement effectuer des opérations fastidieuses dans le gestionnaire d'événements, ce qui peut bloquer le thread qui diffuse.
Que puis-je faire pour que je puisse diffuser les données à d'autres abonnés mais que je puisse quand même diffuser les trucs assez rapidement ??
100 ns est une cible très difficile à atteindre. Je crois qu'il faudra une compréhension approfondie de ce que vous faites et pourquoi atteindre ce type de performance.
Cependant, l'invocation asynchrone d'abonnés à un événement est assez facile à résoudre. Il est déjà répondu ici par, qui d'autre, Jon Skeet.
foreach (MyDelegate action in multicast.GetInvocationList())
{
action.BeginInvoke(...);
}
edit: Je dois également mentionner que vous devez exécuter un système d'exploitation en temps réel pour donner des garanties de performances strictes à vos utilisateurs.
Il semble que vous recherchiez des tâches. Ce qui suit est une méthode d'extension que j'ai écrite pour mon travail qui peut invoquer de manière asynchrone un événement afin que chaque gestionnaire d'événements se trouve sur son propre thread. Je ne peux pas commenter sa vitesse car cela n'a jamais été une exigence pour moi.
METTRE À JOUR
Sur la base des commentaires, je l'ai ajusté de sorte qu'une seule tâche soit créée pour appeler tous les abonnés
/// <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);
}
});
}
}
Vous pouvez utiliser ces méthodes d'extension simples sur vos gestionnaires d'événements:
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());
}
Usage:
public static Test() {
myEventHandler.RaiseOnDifferentThread(null, EventArgs.Empty);
}
cancellationToken
est nécessaire pour garantir que StartNew()
utilise réellement un thread différent, comme expliqué ici .
Je ne peux pas vous dire si cela répondra de manière fiable à l'exigence de 100ns, mais voici une alternative où vous fourniriez à l'utilisateur final un moyen de vous fournir une file d'attente simultanée que vous rempliriez et qu'il pourrait écouter sur un thread séparé.
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; }
}
Étant donné que vous ne pouvez pas bloquer le traitement sur d'autres écouteurs, cela signifie plusieurs threads. Avoir des threads d'écoute dédiés sur les écouteurs semble être une approche raisonnable à essayer, et la file d'attente simultanée semble être un mécanisme de livraison décent. Dans cette implémentation, il s'agit simplement d'interroger constamment, mais vous pouvez probablement utiliser la signalisation des threads pour réduire la charge du processeur en utilisant quelque chose comme AutoResetEvent
.
Les signaux et la mémoire partagée sont très rapides. Vous pouvez envoyer un signal distinct pour dire aux applications de lire un message à partir d'un emplacement de mémoire partagée. Bien sûr, le signal est toujours un événement que votre application doit consommer sur un thread de haute priorité si vous voulez une faible latence. Je voudrais inclure un horodatage dans les données afin que le récepteur puisse compenser la latence inévitable.