Ich arbeite an einem Multitasking-Netzwerkprojekt und bin neu bei Threading.Tasks
. Ich habe eine einfache implementiert Task.Factory.StartNew()
und frage mich, wie ich das machen kann Task.Run()
.
Hier ist der Basiscode:
Task.Factory.StartNew(new Action<object>(
(x) =>
{
// Do something with 'x'
}), rawData);
Ich habe System.Threading.Tasks.Task
im Objektbrowser nachgesehen und konnte keinen Action<T>
ähnlichen Parameter finden. Es gibt nur Action
das nimmt void
Parameter und keine Art .
Es gibt nur 2 Dinge ähnlich: static Task Run(Action action)
und static Task Run(Func<Task> function)
kann aber nicht Post - Parameter (n) mit beiden.
Ja, ich weiß, dass ich eine einfache Erweiterungsmethode dafür erstellen kann, aber meine Hauptfrage ist, ob wir sie in einer Zeile mit schreiben könnenTask.Run()
.
c#
lambda
task-parallel-library
task
MFatihMAR
quelle
quelle
rawData
ist ein Netzwerkdatenpaket mit einer Containerklasse (wie DataPacket), und ich verwende diese Instanz erneut, um den GC-Druck zu reduzieren. Wenn ich alsorawData
direkt in verwendeTask
, kann es (wahrscheinlich) geändert werden, bevor esTask
behandelt wird. Jetzt denke ich, dass ich eine anderebyte[]
Instanz dafür erstellen kann. Ich denke, es ist die einfachste Lösung für mich.Action<byte[]>
ändert daran nichts.Antworten:
private void RunAsync() { string param = "Hi"; Task.Run(() => MethodWithParameter(param)); } private void MethodWithParameter(string param) { //Do stuff }
Bearbeiten
Aufgrund der großen Nachfrage muss ich beachten, dass der
Task
Start parallel zum aufrufenden Thread ausgeführt wird. Unter der Annahme der StandardeinstellungTaskScheduler
wird .NET verwendetThreadPool
. Auf jeden Fall bedeutet dies, dass Sie berücksichtigen müssen, welche Parameter an dieTask
potenziell als von mehreren Threads gleichzeitig zugegriffen werden, sodass sie gemeinsam genutzt werden. Dies beinhaltet den Zugriff auf sie im aufrufenden Thread.In meinem obigen Code ist dieser Fall völlig umstritten. Saiten sind unveränderlich. Deshalb habe ich sie als Beispiel genommen. Aber sagen Sie, Sie verwenden keine
String
...Eine Lösung ist die Verwendung von
async
undawait
. Dies erfasst standardmäßigSynchronizationContext
den aufrufenden Thread und erstellt nach dem Aufruf eine Fortsetzung für den Rest der Methodeawait
und hängt sie an den erstellten anTask
. Wenn diese Methode auf dem WinForms-GUI-Thread ausgeführt wird, ist sie vom TypWindowsFormsSynchronizationContext
.Die Fortsetzung wird ausgeführt, nachdem sie wieder in den erfassten Bereich gebucht wurde
SynchronizationContext
- wiederum nur standardmäßig. Sie befinden sich also wieder in dem Thread, mit dem Sie nach demawait
Aufruf begonnen haben. Sie können dies auf verschiedene Arten ändern, insbesondere mitConfigureAwait
. Kurz gesagt, der Rest dieser Methode wird erst fortgesetzt, nachdem derTask
Vorgang in einem anderen Thread abgeschlossen wurde. Der aufrufende Thread wird jedoch weiterhin parallel ausgeführt, nur nicht der Rest der Methode.Dieses Warten auf den Abschluss des Restes der Methode kann wünschenswert sein oder auch nicht. Wenn nichts in dieser Methode später auf die Parameter zugreift, die an übergeben wurden,
Task
möchten Sie sie möglicherweise überhaupt nicht verwendenawait
.Oder vielleicht verwenden Sie diese Parameter viel später in der Methode. Kein Grund,
await
sofort, da Sie sicher weiterarbeiten können. Denken Sie daran, dass Sie die zurückgegebenenTask
Daten in einer Variablen undawait
später darauf speichern können - auch mit derselben Methode. Zum Beispiel, wenn Sie nach einer Reihe anderer Arbeiten sicher auf die übergebenen Parameter zugreifen müssen. Auch hier haben Sie nicht brauchen , umawait
auf demTask
Recht , wenn Sie es starten.Eine einfache Möglichkeit, diesen Thread in Bezug auf die übergebenen Parameter sicher zu machen,
Task.Run
besteht darin, Folgendes zu tun:Sie müssen zuerst dekorieren
RunAsync
mitasync
:private async void RunAsync()
Wichtige Notiz
Vorzugsweise sollte die markierte Methode nicht ungültig sein, wie in der verknüpften Dokumentation erwähnt. Die häufigste Ausnahme sind Ereignishandler wie Schaltflächenklicks und dergleichen. Sie müssen ungültig zurückkehren. Ansonsten versuche ich immer ein oder bei Verwendung zurückzugeben . Es ist aus mehreren Gründen eine gute Übung.
async
Task
Task<TResult>
async
Jetzt können Sie
await
dasTask
Folgende unten ausführen. Sie können nichtawait
ohne verwendenasync
.await Task.Run(() => MethodWithParameter(param)); //Code here and below in the same method will not run until AFTER the above task has completed in one fashion or another
Wenn Sie also
await
die Aufgabe übernehmen, können Sie im Allgemeinen vermeiden, übergebene Parameter als potenziell gemeinsam genutzte Ressource zu behandeln, mit all den Gefahren, etwas aus mehreren Threads gleichzeitig zu ändern. Achten Sie auch auf Verschlüsse . Ich werde nicht näher darauf eingehen, aber der verlinkte Artikel leistet hervorragende Arbeit.Randnotiz
Ein bisschen abseits des Themas, aber seien Sie vorsichtig, wenn Sie den WinForms-GUI-Thread mit "Blockieren" versehen, da er mit markiert ist
[STAThread]
. Die Verwendungawait
wird überhaupt nicht blockiert, aber ich sehe manchmal, dass sie in Verbindung mit einer Art Blockierung verwendet wird."Blockieren" steht in Anführungszeichen, da Sie den WinForms-GUI-Thread technisch nicht blockieren können . Ja, wenn Sie
lock
den WinForms-GUI-Thread verwenden, werden weiterhin Nachrichten gepumpt, obwohl Sie denken, dass er "blockiert" ist. Es ist nicht.Dies kann in sehr seltenen Fällen bizarre Probleme verursachen. Einer der Gründe, warum Sie
lock
zum Beispiel beim Malen niemals einen verwenden möchten . Aber das ist ein Randfall und ein komplexer Fall. Ich habe jedoch gesehen, dass es verrückte Probleme verursacht. Der Vollständigkeit halber habe ich es notiert.quelle
Task.Run(() => MethodWithParameter(param));
. Dies bedeutet, dass bei einerparam
Änderung nach demTask.Run
möglicherweise unerwartete Ergebnisse auftretenMethodWithParameter
.Verwenden Sie die Variablenerfassung, um Parameter zu "übergeben".
var x = rawData; Task.Run(() => { // Do something with 'x' });
Sie können auch
rawData
direkt verwenden, müssen jedoch vorsichtig sein. Wenn Sie den WertrawData
außerhalb einer Aufgabe ändern (z. B. einen Iterator in einerfor
Schleife), wird auch der Wert innerhalb der Aufgabe geändert.quelle
Task.Run
.Ich weiß, dass dies ein alter Thread ist, aber ich wollte eine Lösung teilen, die ich letztendlich verwenden musste, da der akzeptierte Beitrag immer noch ein Problem aufweist.
Das Thema:
Wie Alexandre Severino
param
hervorhob, kann es zu unerwarteten Verhaltensweisen kommen , wenn sich (in der folgenden Funktion) kurz nach dem Funktionsaufruf ändertMethodWithParameter
.Meine Lösung:
Um dies zu erklären, habe ich am Ende etwas mehr wie die folgende Codezeile geschrieben:
(new Func<T, Task>(async (p) => await Task.Run(() => MethodWithParam(p)))).Invoke(param);
Dadurch konnte ich den Parameter sicher asynchron verwenden, obwohl sich der Parameter nach dem Start der Aufgabe sehr schnell änderte (was zu Problemen mit der veröffentlichten Lösung führte).
Bei Verwendung dieses Ansatzes wird
param
(Werttyp) der Wert übergeben. Selbst wenn die asynchrone Methode nachparam
Änderungen ausgeführtp
wird, hat sie den Wert,param
den diese Codezeile hatte.quelle
var localParam = param; await Task.Run(() => MethodWithParam(localParam));
Ab sofort können Sie auch:
Action<int> action = (o) => Thread.Sleep(o); int param = 10; await new TaskFactory().StartNew(action, param)
quelle
Verwenden Sie einfach Task.Run
var task = Task.Run(() => { //this will already share scope with rawData, no need to use a placeholder });
Oder wenn Sie es in einer Methode verwenden und die Aufgabe später abwarten möchten
public Task<T> SomethingAsync<T>() { var task = Task.Run(() => { //presumably do something which takes a few ms here //this will share scope with any passed parameters in the method return default(T); }); return task; }
quelle
for(int rawData = 0; rawData < 10; ++rawData) { Task.Run(() => { Console.WriteLine(rawData); } ) }
es sich nicht so verhält, als obrawData
es wie im StartNew-Beispiel des OP übergeben wurde.Es ist unklar, ob das ursprüngliche Problem das gleiche Problem war, das ich hatte: Ich wollte die CPU-Threads bei der Berechnung innerhalb einer Schleife maximieren, während der Wert des Iterators beibehalten und inline gehalten wurde, um zu vermeiden, dass eine Menge Variablen an eine Worker-Funktion übergeben werden.
for (int i = 0; i < 300; i++) { Task.Run(() => { var x = ComputeStuff(datavector, i); // value of i was incorrect var y = ComputeMoreStuff(x); // ... }); }
Ich habe dies zum Laufen gebracht, indem ich den äußeren Iterator geändert und seinen Wert mit einem Gate lokalisiert habe.
for (int ii = 0; ii < 300; ii++) { System.Threading.CountdownEvent handoff = new System.Threading.CountdownEvent(1); Task.Run(() => { int i = ii; handoff.Signal(); var x = ComputeStuff(datavector, i); var y = ComputeMoreStuff(x); // ... }); handoff.Wait(); }
quelle
Die Idee ist, die Verwendung eines Signals wie oben zu vermeiden. Das Pumpen von int-Werten in eine Struktur verhindert, dass sich diese Werte (in der Struktur) ändern. Ich hatte das folgende Problem: loop var i würde sich ändern, bevor DoSomething (i) aufgerufen wurde (i wurde am Ende der Schleife erhöht, bevor () => DoSomething (i, i i) aufgerufen wurde). Mit den Strukturen passiert es nicht mehr. Böser Fehler zu finden: DoSomething (i, i i) sieht gut aus, ist sich aber nie sicher, ob es jedes Mal mit einem anderen Wert für i (oder nur 100 Mal mit i = 100) aufgerufen wird, daher -> struct
struct Job { public int P1; public int P2; } … for (int i = 0; i < 100; i++) { var job = new Job { P1 = i, P2 = i * i}; // structs immutable... Task.Run(() => DoSomething(job)); }
quelle