web-dev-qa-db-fra.com

File d'attente thread-safe

Tout d'abord, je vais vous expliquer un court scénario.

Lorsqu'un signal provenant de certains périphériques se déclenche, un objet de type Alarm est ajouté à une file d'attente. À intervalles réguliers, la file d'attente est vérifiée et, pour chaque alarme de la file d'attente, une méthode est déclenchée.

Cependant, le problème que je rencontre est que, si une alarme est ajoutée à la file d'attente pendant qu'elle est traversée, une erreur est générée pour indiquer que la file d'attente a changé pendant que vous l'utilisiez. Voici un peu de code pour montrer ma file d'attente, supposons simplement que des alarmes y sont constamment insérées;

public class AlarmQueueManager
{
    public ConcurrentQueue<Alarm> alarmQueue = new ConcurrentQueue<Alarm>();
    System.Timers.Timer timer;

    public AlarmQueueManager()
    {
        timer = new System.Timers.Timer(1000);
        timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
        timer.Enabled = true;
    }

    void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
    {
        DeQueueAlarm();
    }

    private void DeQueueAlarm()
    {
        try
        {
            foreach (Alarm alarm in alarmQueue)
            {
                SendAlarm(alarm);
                alarmQueue.TryDequeue();
                //having some trouble here with TryDequeue..

            }
        }
        catch
        {
        }
    }

Ma question est donc la suivante: comment rendre ce fil plus sûr? Pour que je ne rencontre pas ces problèmes. Peut-être que vous pourriez copier la file d’attente dans une autre file, y travailler, puis mettre de côté les alarmes traitées dans la file initiale?

edit: vient d'être informé de la file d'attente simultanée, va vérifier cela maintenant

23
Shane.C
private void DeQueueAlarm()
{
    Alarm alarm;
    while (alarmQueue.TryDequeue(out alarm))
        SendAlarm(alarm);
}

Alternativement, vous pouvez utiliser:

private void DeQueueAlarm()
{
    foreach (Alarm alarm in alarmQueue)
        SendAlarm(alarm);
}

Selon l'article MSDN sur ConcurrentQueue<T>.GetEnumerator :

L'énumération représente un instantané instantané du contenu de la file d'attente. Il ne reflète aucune mise à jour de la collection après l'appel de GetEnumerator. L'énumérateur peut être utilisé en même temps que des lectures et des écritures dans la file d'attente.

Ainsi, la différence entre les deux approches survient lorsque votre méthode DeQueueAlarm est appelée simultanément par plusieurs threads. En utilisant l'approche TryQueue, vous êtes assuré que chaque Alarm de la file d'attente ne sera traité qu'une seule fois; Cependant, quel thread sélectionne quelle alarme est déterminée de manière non déterministe. L'approche foreach permet de s'assurer que chaque fil d'exécution traitera toutes les alarmes de la file d'attente (à partir du moment où il a commencé à les parcourir), ce qui entraînera le traitement de la même alarme plusieurs fois.

Si vous souhaitez traiter chaque alarme exactement une fois, puis la supprimer de la file d'attente, vous devez utiliser la première approche.

23
Douglas

Toute raison pour laquelle vous ne pouvez pas utiliser ConcurrentQueue<T>

21
hometoast

.Net a déjà une implémentation de file d’alarme thread-safe: regardez ConcurrentQueue .

7
jeroenh

Une meilleure façon de l'aborder, étant donné que chaque thread ne traite en réalité qu'une seule alarme à la fois, remplacerait ceci:

        foreach (Alarm alarm in alarmQueue)
        {
            SendAlarm(alarm);
            alarmQueue.TryDequeue();
            //having some trouble here with TryDequeue..
        }

avec ça:

        while (!alarmQueue.IsEmpty)
        {
            Alarm alarm;
            if (!alarmQueue.TryDequeue(out alarm))  continue;
            SendAlarm(alarm);
        }

Il n'y a aucune raison d'obtenir un instantané complet de la file d'attente à tout moment, car vous ne vous souciez que de la suivante à traiter au début de chaque cycle.

0
Thought