Task.Run mit Parameter (n)?

87

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.Taskim Objektbrowser nachgesehen und konnte keinen Action<T>ähnlichen Parameter finden. Es gibt nur Actiondas nimmt voidParameter 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() .

MFatihMAR
quelle
Es ist nicht klar, wie der Wert des Parameters aussehen soll. Woher würde es kommen? Wenn Sie es bereits haben, erfassen Sie es einfach im Lambda-Ausdruck ...
Jon Skeet
@JonSkeet rawDataist ein Netzwerkdatenpaket mit einer Containerklasse (wie DataPacket), und ich verwende diese Instanz erneut, um den GC-Druck zu reduzieren. Wenn ich also rawDatadirekt in verwende Task, kann es (wahrscheinlich) geändert werden, bevor es Taskbehandelt wird. Jetzt denke ich, dass ich eine andere byte[]Instanz dafür erstellen kann. Ich denke, es ist die einfachste Lösung für mich.
MFatihMAR
Ja, wenn Sie das Byte-Array klonen müssen, klonen Sie das Byte-Array. Ein zu haben Action<byte[]>ändert daran nichts.
Jon Skeet
Hier sind einige gute Lösungen , um Parameter an eine Aufgabe zu übergeben.
Just Shadow

Antworten:

115
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 TaskStart parallel zum aufrufenden Thread ausgeführt wird. Unter der Annahme der Standardeinstellung TaskSchedulerwird .NET verwendet ThreadPool. Auf jeden Fall bedeutet dies, dass Sie berücksichtigen müssen, welche Parameter an die Taskpotenziell 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 asyncund await. Dies erfasst standardmäßig SynchronizationContextden aufrufenden Thread und erstellt nach dem Aufruf eine Fortsetzung für den Rest der Methode awaitund hängt sie an den erstellten an Task. Wenn diese Methode auf dem WinForms-GUI-Thread ausgeführt wird, ist sie vom Typ WindowsFormsSynchronizationContext.

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 dem awaitAufruf begonnen haben. Sie können dies auf verschiedene Arten ändern, insbesondere mit ConfigureAwait. Kurz gesagt, der Rest dieser Methode wird erst fortgesetzt, nachdem der TaskVorgang 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, Taskmöchten Sie sie möglicherweise überhaupt nicht verwenden await.

Oder vielleicht verwenden Sie diese Parameter viel später in der Methode. Kein Grund, awaitsofort, da Sie sicher weiterarbeiten können. Denken Sie daran, dass Sie die zurückgegebenen TaskDaten in einer Variablen und awaitspä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 , um awaitauf dem TaskRecht , wenn Sie es starten.

Eine einfache Möglichkeit, diesen Thread in Bezug auf die übergebenen Parameter sicher zu machen, Task.Runbesteht darin, Folgendes zu tun:

Sie müssen zuerst dekorieren RunAsyncmit async:

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 TaskTask<TResult>async

Jetzt können Sie awaitdas TaskFolgende unten ausführen. Sie können nicht awaitohne verwenden async.

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 awaitdie 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 Verwendung awaitwird ü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 lockden 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 lockzum 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.

Zer0
quelle
20
Sie warten nicht Task.Run(() => MethodWithParameter(param));. Dies bedeutet, dass bei einer paramÄnderung nach dem Task.Runmöglicherweise unerwartete Ergebnisse auftreten MethodWithParameter.
Alexandre Severino
7
Warum ist dies eine akzeptierte Antwort, wenn es falsch ist? Es ist überhaupt nicht gleichbedeutend mit dem Übergeben eines Zustandsobjekts.
Egor Pavlikhin
6
@ Zer0 Ein Statusobjekt ist das zweite Paremeter in Task.Factory.StartNew msdn.microsoft.com/en-us/library/dd321456(v=vs.110).aspx und speichert den Wert des Objekts zum Zeitpunkt des Rufen Sie StartNew auf, während Ihre Antwort einen Abschluss erstellt, der die Referenz beibehält (wenn sich der Wert von param ändert, bevor die Aufgabe ausgeführt wird, ändert sich dies auch in der Aufgabe), sodass Ihr Code überhaupt nicht dem entspricht, was die Frage gestellt hat . Die Antwort ist wirklich, dass es keine Möglichkeit gibt, es mit Task.Run () zu schreiben.
Egor Pavlikhin
2
@ Zer0 für structs Task.Run mit Verschluss und Task.Factory.StartNew mit 2.en Parametern (die pro Ihrem Link nicht die gleichen wie Task.Run ist) wird sich anders verhalten, im letzteren Fall , da eine Kopie gemacht werden. Mein Fehler bestand darin, mich im ursprünglichen Kommentar allgemein auf Objekte zu beziehen. Ich meinte damit, dass sie nicht vollständig gleichwertig sind.
Egor Pavlikhin
3
Beim Lesen von Toubs Artikel werde ich diesen Satz hervorheben: "Sie können Überladungen verwenden, die den Objektstatus akzeptieren, und die für leistungsabhängige Codepfade verwendet werden können, um Schließungen und die entsprechenden Zuordnungen zu vermeiden." Ich denke, dies ist, was @Zero impliziert, wenn Task.Run over StartNew-Nutzung in Betracht gezogen wird.
Davidcarr
34

Verwenden Sie die Variablenerfassung, um Parameter zu "übergeben".

var x = rawData;
Task.Run(() =>
{
    // Do something with 'x'
});

Sie können auch rawDatadirekt verwenden, müssen jedoch vorsichtig sein. Wenn Sie den Wert rawDataaußerhalb einer Aufgabe ändern (z. B. einen Iterator in einer forSchleife), wird auch der Wert innerhalb der Aufgabe geändert.

Scott Chamberlain
quelle
11
+1 für die Berücksichtigung der wichtigen Tatsache, dass die Variable direkt nach dem Aufruf geändert werden kann Task.Run.
Alexandre Severino
1
Wie wird das helfen? Wenn Sie x innerhalb des Task-Threads verwenden und x eine Referenz auf ein Objekt ist und das Objekt zur gleichen Zeit geändert wird, während der Task-Thread ausgeführt wird, kann dies zu Chaos führen.
Ovi
1
@ Ovi-WanKenobi Ja, aber darum ging es bei dieser Frage nicht. Es war, wie man einen Parameter übergibt. Wenn Sie einen Verweis auf ein Objekt als Parameter an eine normale Funktion übergeben würden, hätten Sie auch dort genau das gleiche Problem.
Scott Chamberlain
Ja, das funktioniert nicht. Meine Aufgabe hat keinen Verweis auf x im aufrufenden Thread. Ich bekomme nur null.
David Price
7

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 paramhervorhob, kann es zu unerwarteten Verhaltensweisen kommen , wenn sich (in der folgenden Funktion) kurz nach dem Funktionsaufruf ändert MethodWithParameter.

Task.Run(() => MethodWithParameter(param)); 

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 nach paramÄnderungen ausgeführt pwird, hat sie den Wert, paramden diese Codezeile hatte.

Kaden Burgart
quelle
5
Ich erwarte gespannt jeden, der sich einen Weg ausdenken kann, dies mit weniger Aufwand leserlicher zu machen. Das ist zugegebenermaßen ziemlich hässlich.
Kaden Burgart
5
Los geht's:var localParam = param; await Task.Run(() => MethodWithParam(localParam));
Stephen Cleary
1
Was Stephen übrigens bereits vor anderthalb Jahren in seiner Antwort besprochen hat.
Servy
1
@Servy: Das war eigentlich Scotts Antwort . Ich habe diesen nicht beantwortet.
Stephen Cleary
Scotts Antwort hätte für mich eigentlich nicht funktioniert, da ich dies in einer for-Schleife ausgeführt habe. Der lokale Parameter wäre in der nächsten Iteration zurückgesetzt worden. Der Unterschied in der Antwort, die ich gepostet habe, besteht darin, dass der Parameter in den Bereich des Lambda-Ausdrucks kopiert wird, sodass die Variable sofort sicher ist. In Scotts Antwort befindet sich der Parameter immer noch im selben Bereich, sodass er sich zwischen dem Aufrufen der Leitung und dem Ausführen der asynchronen Funktion ändern kann.
Kaden Burgart
6

Ab sofort können Sie auch:

Action<int> action = (o) => Thread.Sleep(o);
int param = 10;
await new TaskFactory().StartNew(action, param)
Arnaud F.
quelle
5

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;
}
Travis J.
quelle
1
Achten Sie nur auf Schließungen, wenn Sie dies so tun, dass for(int rawData = 0; rawData < 10; ++rawData) { Task.Run(() => { Console.WriteLine(rawData); } ) }es sich nicht so verhält, als ob rawDataes wie im StartNew-Beispiel des OP übergeben wurde.
Scott Chamberlain
@ScottChamberlain - Das scheint ein anderes Beispiel zu sein;) Ich würde hoffen, dass die meisten Leute verstehen, wie man über Lambda-Werte schließt.
Travis J
3
Und wenn diese vorherigen Kommentare keinen Sinn ergeben haben, lesen Sie bitte Eric Lippers Blog zum Thema: blogs.msdn.com/b/ericlippert/archive/2009/11/12/… Es erklärt, warum dies sehr gut passiert.
Travis J
2

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();
}
Harald J.
quelle
0

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));
}
CodeDigger
quelle
1
Dies kann zwar die Frage beantworten, wurde jedoch zur Überprüfung markiert. Antworten ohne Erklärung werden oft als minderwertig angesehen. Bitte kommentieren Sie, warum dies die richtige Antwort ist.
Dan