Ich bin ein Software-/Hardware-Ingenieur mit einiger Erfahrung in C- und Embedded-Technologien. Derzeit bin ich damit beschäftigt, einige Anwendungen in C # (.NET) zu schreiben, die Hardware zur Datenerfassung verwenden. Nun die folgende für mich brennende Frage:
Zum Beispiel: Ich habe eine Maschine, die über einen Endschalter verfügt, um die Endposition einer Achse zu ermitteln. Jetzt verwende ich ein USB-Datenerfassungsmodul zum Lesen der Daten. Derzeit verwende ich einen Thread, um kontinuierlich den Portstatus zu lesen.
Auf diesem Gerät gibt es keine Interrupt-Funktion.
Meine Frage: Ist das der richtige Weg? Soll ich Timer, Threads oder Aufgaben verwenden? Ich weiß, dass Umfragen etwas sind, das die meisten von euch "hassen", aber jeder Vorschlag ist willkommen!
IMO, das hängt stark von Ihrer genauen Umgebung ab, aber zunächst einmal - Sie sollten Threads in den meisten Fällen nicht mehr verwenden. Tasks
sind dafür die bequemere und leistungsfähigere Lösung.
Niedrige Abrufhäufigkeit: Timer + Abfrage im Ereignis Tick
:
Ein Timer ist einfach zu handhaben und zu stoppen. Sie müssen sich keine Gedanken darüber machen, dass Threads/Aufgaben im Hintergrund ausgeführt werden. Die Handhabung erfolgt jedoch im Haupt-Thread
Mittlere Abrufhäufigkeit: Task
+ await Task.Delay(delay)
:await Task.Delay(delay)
blockiert keinen Thread-Pool-Thread, aber aufgrund des Kontextwechsels beträgt die minimale Verzögerung ~ 15ms
Hohe Abrufhäufigkeit: Task
+ Thread.Sleep(delay)
verwendbar bei Verzögerungen von 1 ms - dies geschieht tatsächlich, um unser USB-Messgerät abzufragen
Dies könnte wie folgt implementiert werden:
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);
In den meisten Fällen können Sie einfach Task.Run(() => DoWork(), token)
verwenden, aber es gibt keine Überladung, um die Option TaskCreationOptions.LongRunning
bereitzustellen, die den Taskplaner anweist, keinen normalen Thread-Pool-Thread zu verwenden.
Wie Sie sehen, sind Tasks
einfacher zu handhaben (und await
able, trifft hier aber nicht zu). Insbesondere das "Stoppen" ruft in dieser Implementierung cancellationTokenSource.Cancel()
von überall im Code auf.
Sie können dieses Token sogar in mehreren Aktionen freigeben und gleichzeitig stoppen. Noch nicht gestartete Aufgaben werden auch nicht gestartet, wenn das Token abgebrochen wird.
Sie können auch eine andere Aktion an eine Aufgabe anhängen, um sie nach einer Aufgabe auszuführen:
listener.ContinueWith(t => ShutDown(t));
Dies wird dann ausgeführt, nachdem der Listener abgeschlossen wurde und Sie können eine Bereinigung durchführen (t.Exception
enthält die Ausnahme der Task-Aktion, falls diese nicht erfolgreich war).
IMO-Abfrage kann nicht vermieden werden.
Sie können ein Modul mit einem unabhängigen Thread/Task erstellen, der den Port regelmäßig abfragt. Basierend auf der Änderung der Daten wird dieses Ereignis das Ereignis auslösen, das von den konsumierenden Anwendungen behandelt wird
Könnte sein:
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();
}
}
}
Schauen Sie in SpinWait oder in Task.Delay Interna.