Je suis un ingénieur logiciel/matériel avec une certaine expérience des technologies C et embarquées. Actuellement, je suis occupé à écrire des applications en C # (.NET) qui utilisent du matériel pour l’acquisition de données. Maintenant, la question suivante, pour moi brûlant, question:
Par exemple: j'ai une machine qui a un fin de course pour détecter la position finale d'un axe. Maintenant, j'utilise un module d'acquisition de données USB pour lire les données. Actuellement, j'utilise un thread pour lire en permanence l'état du port.
Il n'y a pas de fonctionnalité d'interruption sur ce périphérique.
Ma question: est-ce la bonne façon? Devrais-je utiliser des minuteries, des discussions ou des tâches? Je sais que les sondages sont quelque chose que la plupart d'entre vous "détestent", mais toute suggestion est la bienvenue!
OMI, cela dépend énormément de votre environnement exact, mais tout d’abord, vous ne devriez plus utiliser Threads dans la plupart des cas. Tasks
sont la solution la plus pratique et la plus puissante pour cela.
Faible fréquence d'interrogation: Minuterie + interrogation dans l'événement Tick
:
Une minuterie est facile à manipuler et à arrêter. Pas besoin de s'inquiéter des threads/tâches s'exécutant en arrière-plan, mais le traitement se fait dans le thread principal
Fréquence d'interrogation moyenne: Task
+ await Task.Delay(delay)
:await Task.Delay(delay)
ne bloque pas un thread de pool de threads, mais en raison du changement de contexte, le délai minimum est d'environ 15 ms.
Fréquence de scrutation élevée: Task
+ Thread.Sleep(delay)
utilisable avec un délai de 1 ms - nous procédons ainsi pour interroger notre périphérique de mesure USB
Cela pourrait être mis en œuvre comme suit:
int delay = 1;
var cancellationTokenSource = new CancellationTokenSource();
var token = cancellationTokenSource.Token;
var listener = Task.Factory.StartNew(() =>
{
while (true)
{
// poll hardware
Thread.Sleep(delay);
if (token.IsCancellationRequested)
break;
}
// cleanup, e.g. close connection
}, token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
Dans la plupart des cas, vous pouvez simplement utiliser Task.Run(() => DoWork(), token)
, mais il n'y a pas de surcharge pour fournir l'option TaskCreationOptions.LongRunning
qui indique au planificateur de tâches de ne pas utiliser un thread de pool de threads normal.
Mais comme vous le voyez, Tasks
sont plus faciles à manipuler (et await
able, mais ne s'applique pas ici). En particulier, le "arrêt" appelle simplement cancellationTokenSource.Cancel()
dans cette implémentation à partir de n’importe où dans le code.
Vous pouvez même partager ce jeton dans plusieurs actions et les arrêter en même temps. En outre, les tâches non encore démarrées ne sont pas démarrées lorsque le jeton est annulé.
Vous pouvez également attacher une autre action à une tâche à exécuter après une tâche:
listener.ContinueWith(t => ShutDown(t));
Cette opération est ensuite exécutée une fois le programme d'écoute terminé et vous pouvez effectuer un nettoyage (t.Exception
contient l'exception de l'action tâches si elle n'a pas abouti).
L’interrogation par l’OMI ne peut être évitée.
Ce que vous pouvez faire est de créer un module, avec son thread/tâche indépendant qui interrogera le port régulièrement. Sur la base de la modification des données, ce module déclenchera l'événement qui sera géré par les applications consommatrices.
Peut être:
public async Task Poll(Func<bool> condition, TimeSpan timeout, string message = null)
{
// https://github.com/dotnet/corefx/blob/3b24c535852d19274362ad3dbc75e932b7d41766/src/Common/src/CoreLib/System/Threading/ReaderWriterLockSlim.cs#L233
var timeoutTracker = new TimeoutTracker(timeout);
while (!condition())
{
await Task.Yield();
if (timeoutTracker.IsExpired)
{
if (message != null) throw new TimeoutException(message);
else throw new TimeoutException();
}
}
}
Regardez dans SpinWait ou dans Task.Delay internals non plus.