ManualResetEvent vs. Thread.Sleep
J'ai mis en œuvre dans le contexte suivant thread de traitement, où Jobs
est un Queue<T>
:
static void WorkThread()
{
while (working)
{
var job;
lock (Jobs)
{
if (Jobs.Count > 0)
job = Jobs.Dequeue();
}
if (job == null)
{
Thread.Sleep(1);
}
else
{
//[snip]: Process job.
}
}
}
Ce produit un notable retard entre le moment où les travaux ont été saisies et quand ils étaient en train de commencer à être exécuté (lots de travaux sont inscrits à la fois, et chaque travail est seulement [relativement] petit). Le retard n'était pas une affaire énorme, mais j'ai eu à réfléchir au problème, et apporté la modification suivante:
static ManualResetEvent _workerWait = new ManualResetEvent(false);
//...
if (job == null)
{
lock (_workerWait)
{
_workerWait.Reset();
}
_workerWait.WaitOne();
}
Où le fil de l'ajout des emplois dès maintenant serrures _workerWait
et les appels _workerWait.Set()
quand c'est fait, l'ajout d'emplois. Cette solution de (presque) instantanément commence les travaux de traitement, et le retard est disparu complètement.
Ma question est en partie "Pourquoi cela?", accordé que Thread.Sleep(int)
peut très bien dormir plus longtemps que vous spécifiez, et en partie "Comment le ManualResetEvent
atteindre ce niveau de performance?".
EDIT: Depuis quelqu'un a demandé à propos de la fonction de mise en queue des articles, c'est ici, le long avec un système complet en ce moment.
public void RunTriggers(string data)
{
lock (this.SyncRoot)
{
this.Triggers.Sort((a, b) => { return a.Priority - b.Priority; });
foreach (Trigger trigger in this.Triggers)
{
lock (Jobs)
{
Jobs.Enqueue(new TriggerData(this, trigger, data));
_workerWait.Set();
}
}
}
}
static private ManualResetEvent _workerWait = new ManualResetEvent(false);
static void WorkThread()
{
while (working)
{
TriggerData job = null;
lock (Jobs)
{
if (Jobs.Count > 0)
job = Jobs.Dequeue();
if (job == null)
{
_workerWait.Reset();
}
}
if (job == null)
_workerWait.WaitOne();
else
{
try
{
foreach (Match m in job.Trigger.Regex.Matches(job.Data))
job.Trigger.Value.Action(job.World, m);
}
catch (Exception ex)
{
job.World.SendLineToClient("\r\n\x1B[32m -- {0} in trigger ({1}): {2}\x1B[m",
ex.GetType().ToString(), job.Trigger.Name, ex.Message);
}
}
}
}
source d'informationauteur Matthew Scharley
Vous devez vous connecter pour publier un commentaire.
Les événements sont le noyau des primitives fournies par le système d'exploitation/Kernel qui est conçu juste pour ce genre de choses. Le noyau fournit une limite sur laquelle vous pouvez garantir les opérations atomiques qui est important pour la synchronisation(Certains l'atomicité peut être fait dans l'espace utilisateur trop avec support matériel).
En bref, lorsqu'un thread attend un événement, il est mis sur une liste d'attente pour l'événement et marquée comme non-exécutable.
Lorsque l'événement est signalé, le noyau se réveille ceux de la liste d'attente et les marque comme exécutables, et ils peuvent continuer à s'exécuter. C'est naturellement un énorme avantage qu'un thread peut se réveiller immédiatement lorsque l'événement est signalé, vs de sommeil pendant une longue période et revérifier l'état de chaque maintenant et puis.
Même de la milliseconde est une très très longue période de temps, vous pourriez avoir traité des milliers de cas en ce moment. Aussi le temps de résolution est traditionnellement 10ms, de sorte que dormir moins de 10ms généralement seulement les résultats dans un 10ms dormir de toute façon. Avec un événement, un thread peut être réveillé et programmé immédiatement
Première de verrouillage sur
_workerWait
est inutile, un Événement est un système (noyau) de l'objet conçu pour la signalisation entre les threads (et très utilisé dans l'API Win32 pour les opérations asynchrones). Par conséquent, il est tout à fait sûr pour plusieurs threads pour définir ou réinitialiser sans synchronisation supplémentaire.À votre question principale, besoin de voir la logique de placer les choses dans la file d'attente, et quelques informations sur la quantité de travail effectué pour chaque job est le thread de passer plus de temps les travaux de traitement ou en attente pour le travail).
Probablement la meilleure solution serait d'utiliser une instance de l'objet de verrouillage de l'utilisation
Monitor.Pulse
etMonitor.Wait
comme une variable de condition.Edit: Avec une vue sur le code pour mettre en file d'attente, il semble que la réponse #1116297 a droite: un 1ms délai est trop long à attendre, étant donné que de nombreux éléments de travail sera extrêmement rapide à traiter.
L'approche de disposer d'un mécanisme pour réveiller le thread de travail est correct (comme il n'est pas .NET simultanées de la file d'attente avec un blocage de la résorption de l'opération). Cependant, plutôt que d'utiliser un événement, une variable de condition va être un peu plus efficace (à savoir la non-soutenu cas, il n'a pas besoin d'un noyau de transition):
Moniteur.Attendre permettra de libérer le verrou sur le paramètre, attendre jusqu'à ce que
Pulse()
ouPulseAll()
est appelé à l'encontre de la serrure, puis entrez le verrouillage et le retour. Besoin pour vérifier la condition d'attente car un autre thread pourrait avoir lu l'élément de la file d'attente.