Ich starte eine neue Aufgabe von einer Funktion aus, möchte aber nicht, dass sie auf demselben Thread ausgeführt wird. Es ist mir egal, auf welchem Thread es läuft, solange es ein anderer ist (daher helfen die Informationen in dieser Frage nicht).
Ist mir garantiert, dass der folgende Code immer beendet wird, TestLock
bevor Task t
er erneut eingegeben werden kann? Wenn nicht, welches Entwurfsmuster wird empfohlen, um eine erneute Entität zu verhindern?
object TestLock = new object();
public void Test(bool stop = false) {
Task t;
lock (this.TestLock) {
if (stop) return;
t = Task.Factory.StartNew(() => { this.Test(stop: true); });
}
t.Wait();
}
Bearbeiten: Basierend auf der folgenden Antwort von Jon Skeet und Stephen Toub besteht eine einfache Möglichkeit, einen Wiedereintritt deterministisch zu verhindern, darin, ein CancellationToken zu übergeben, wie in dieser Erweiterungsmethode dargestellt:
public static Task StartNewOnDifferentThread(this TaskFactory taskFactory, Action action)
{
return taskFactory.StartNew(action: action, cancellationToken: new CancellationToken());
}
c#
multithreading
locking
task
task-parallel-library
Erwin Mayer
quelle
quelle
StartNew
. Eine Aufgabe ist als asynchrone Operation definiert, die nicht unbedingt einen neuen Thread impliziert. Möglicherweise wird auch irgendwo ein vorhandener Thread oder eine andere asynchrone Methode verwendet.t.Wait()
mitawait t
.Wait
passt nicht wirklich zur Philosophie von TPL.SynchronizationContext
, um sicherzustellen, dass Aufgaben auf dem UI-Thread ausgeführt werden. Wenn Sie den Code, derStartNew
den UI-Thread aufgerufen hat , so ausführen würden, würden beide auf demselben Thread ausgeführt. Die einzige Garantie ist, dass die Aufgabe ab demStartNew
Aufruf asynchron ausgeführt wird (zumindest wenn Sie keinRunSynchronously
FlagTask.Factory.StartNew(() => { this.Test(stop: true); }, TaskCreationOptions.LongRunning);
Was eine gute Idee ist, wenn Ihre Sperren den Thread für einen längeren Zeitraum in einen Wartezustand versetzen könnten.Antworten:
Ich habe Stephen Toub - ein Mitglied des PFX-Teams - über diese Frage informiert . Er ist sehr schnell und mit vielen Details zu mir zurückgekehrt - also kopiere ich einfach seinen Text und füge ihn hier ein. Ich habe nicht alles zitiert, da das Lesen einer großen Menge zitierten Textes weniger bequem ist als Vanille-Schwarz-auf-Weiß, aber das ist wirklich Stephen - ich weiß nicht so viel :) Ich habe es gemacht Dieses Antwort-Community-Wiki zeigt, dass all die Güte unten nicht wirklich mein Inhalt ist:
EDIT: Okay, es sieht so aus, als hätte ich mich völlig geirrt und ein Thread, der gerade auf eine Aufgabe wartet, kann entführt werden. Hier ist ein einfacheres Beispiel dafür:
using System; using System.Threading; using System.Threading.Tasks; namespace ConsoleApplication1 { class Program { static void Main() { for (int i = 0; i < 10; i++) { Task.Factory.StartNew(Launch).Wait(); } } static void Launch() { Console.WriteLine("Launch thread: {0}", Thread.CurrentThread.ManagedThreadId); Task.Factory.StartNew(Nested).Wait(); } static void Nested() { Console.WriteLine("Nested thread: {0}", Thread.CurrentThread.ManagedThreadId); } } }
Beispielausgabe:
Launch thread: 3 Nested thread: 3 Launch thread: 3 Nested thread: 3 Launch thread: 3 Nested thread: 3 Launch thread: 3 Nested thread: 3 Launch thread: 4 Nested thread: 4 Launch thread: 4 Nested thread: 4 Launch thread: 4 Nested thread: 4 Launch thread: 4 Nested thread: 4 Launch thread: 4 Nested thread: 4 Launch thread: 4 Nested thread: 4
Wie Sie sehen, wird der wartende Thread häufig zur Ausführung der neuen Aufgabe wiederverwendet. Dies kann auch dann passieren, wenn der Thread eine Sperre erhalten hat. Böser Wiedereintritt. Ich bin entsprechend schockiert und besorgt :(
quelle
Wait
:Task.Factory.StartNew(Launch, TaskCreationOptions.LongRunning).Wait()
Wait
während du ein Schloss besitzt. Sperren werden erneut eingegeben. Wenn also die Aufgabe, auf die Sie warten, versucht, die Sperre ebenfalls zu erwerben, ist sie erfolgreich - da sie die Sperre bereits besitzt, obwohl dies logischerweise in einer anderen Aufgabe der Fall ist. Das sollte zum Stillstand kommen, aber es wird nicht ... wenn es wieder eintritt. Grundsätzlich macht mich der Wiedereintritt im Allgemeinen nervös; es fühlt sich an, als würde es alle Arten von Annahmen verletzen.Warum nicht einfach dafür entwerfen, anstatt sich nach hinten zu beugen, um sicherzustellen, dass es nicht passiert?
Die TPL ist hier ein roter Hering. Wiedereintritte können in jedem Code auftreten, vorausgesetzt, Sie können einen Zyklus erstellen, und Sie wissen nicht genau, was "südlich" Ihres Stapelrahmens passieren wird. Synchroner Wiedereintritt ist hier das beste Ergebnis - zumindest können Sie sich nicht (so leicht) selbst blockieren.
Sperren verwalten die Thread-übergreifende Synchronisierung. Sie sind orthogonal zum Management der Wiedereintrittsfähigkeit. Wenn Sie keine echte Ressource für den einmaligen Gebrauch (wahrscheinlich ein physisches Gerät, in diesem Fall sollten Sie wahrscheinlich eine Warteschlange verwenden) schützen, stellen Sie sicher, dass Ihr Instanzstatus konsistent ist, damit die Wiedereintrittsfunktion "einfach funktioniert".
(Nebengedanke: Sind Semaphoren ohne Dekrementierung wiedereintrittsfähig?)
quelle
Sie können dies leicht testen, indem Sie eine schnelle App schreiben, die eine Socket-Verbindung zwischen Threads / Aufgaben gemeinsam nutzt.
Die Aufgabe würde eine Sperre erhalten, bevor eine Nachricht über den Socket gesendet und auf eine Antwort gewartet wird. Sobald dies blockiert und inaktiv wird (IOBlock), setzen Sie eine andere Aufgabe im selben Block, um dasselbe zu tun. Es sollte beim Erwerb der Sperre blockiert werden. Wenn dies nicht der Fall ist und die zweite Task die Sperre übergeben darf, weil sie von demselben Thread ausgeführt wird, liegt ein Problem vor.
quelle
Die
new CancellationToken()
von Erwin vorgeschlagene Lösung funktionierte bei mir nicht, Inlining trat trotzdem auf.Also habe ich eine andere Bedingung verwendet, die von Jon und Stephen (
... or if you pass in a non-infinite timeout ...
) empfohlen wurde :Task<TResult> task = Task.Run(func); task.Wait(TimeSpan.FromHours(1)); // Whatever is enough for task to start return task.Result;
Hinweis: Wenn Sie hier der Einfachheit halber die Ausnahmebehandlung usw. weglassen, sollten Sie die im Produktionscode enthaltenen beachten.
quelle