Bisher habe ich anstelle des Threading-Timers eine LongRunning-TPL-Task für zyklische CPU-gebundene Hintergrundarbeiten verwendet, weil:
- Die TPL-Task unterstützt das Abbrechen
- Der Threading-Timer könnte einen anderen Thread starten, während das Programm heruntergefahren wird, was zu möglichen Problemen mit den verfügbaren Ressourcen führen kann
- Überlaufgefahr: Der Threading-Timer könnte aufgrund unerwartet langer Arbeit einen anderen Thread starten, während der vorherige noch verarbeitet wird (ich weiß, dies kann durch Anhalten und Neustarten des Timers verhindert werden.)
Die TPL-Lösung beansprucht jedoch immer einen dedizierten Thread, der nicht erforderlich ist, während auf die nächste Aktion gewartet wird (was meistens der Fall ist). Ich möchte die vorgeschlagene Lösung von Jeff verwenden, um CPU-gebundene zyklische Arbeit im Hintergrund auszuführen, da nur dann ein Threadpool-Thread benötigt wird, wenn Arbeit zu erledigen ist, die für die Skalierbarkeit besser ist (insbesondere wenn die Intervallperiode groß ist).
Um dies zu erreichen, würde ich 4 Anpassungen vorschlagen:
- Fügen Sie hinzu
ConfigureAwait(false)
, Task.Delay()
um die doWork
Aktion ansonsten für einen Thread-Pool-Thread auszuführendoWork
wird sie für den aufrufenden Thread ausgeführt, was nicht die Idee der Parallelität ist
- Halten Sie sich an das Stornierungsmuster, indem Sie eine TaskCanceledException auslösen (noch erforderlich?)
- Leiten Sie das CancellationToken an weiter
doWork
, damit es die Aufgabe abbrechen kann
- Fügen Sie einen Parameter vom Typ Objekt hinzu, um Informationen zum Aufgabenstatus bereitzustellen (z. B. eine TPL-Aufgabe).
Zu Punkt 2 Ich bin mir nicht sicher, ob für das asynchrone Warten noch die TaskCanceledExecption erforderlich ist, oder handelt es sich nur um eine bewährte Methode?
public static async Task Run(Action<object, CancellationToken> doWork, object taskState, TimeSpan period, CancellationToken cancellationToken)
{
do
{
await Task.Delay(period, cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
doWork(taskState, cancellationToken);
}
while (true);
}
Bitte geben Sie Ihre Kommentare zu der vorgeschlagenen Lösung ...
Update 30.08.2016
Die obige Lösung ruft nicht sofort auf, doWork()
sondern beginnt mit await Task.Delay().ConfigureAwait(false)
dem Erreichen des Thread-Wechsels für doWork()
. Die folgende Lösung überwindet dieses Problem, indem der erste doWork()
Anruf in a eingeschlossen Task.Run()
und abgewartet wird.
Im Folgenden finden Sie die verbesserte asynchrone \ wait-Ersetzung Threading.Timer
, die abbrechbare zyklische Arbeiten ausführt und skalierbar ist (im Vergleich zur TPL-Lösung), da sie beim Warten auf die nächste Aktion keinen Thread belegt.
Beachten Sie, dass im Gegensatz zum Timer die Wartezeit ( period
) konstant ist und nicht die Zykluszeit. Die Zykluszeit ist die Summe der Wartezeit und deren Dauer doWork()
variieren kann.
public static async Task Run(Action<object, CancellationToken> doWork, object taskState, TimeSpan period, CancellationToken cancellationToken)
{
await Task.Run(() => doWork(taskState, cancellationToken), cancellationToken).ConfigureAwait(false);
do
{
await Task.Delay(period, cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
doWork(taskState, cancellationToken);
}
while (true);
}