Je travaille sur un projet de réseau multi-tâches et je suis nouveau Threading.Tasks
. J'ai implémenté un simple Task.Factory.StartNew()
et je me demande comment puis-je le faire avec Task.Run()
?
Voici le code de base:
Task.Factory.StartNew(new Action<object>(
(x) =>
{
// Do something with 'x'
}), rawData);
Je regardais dans System.Threading.Tasks.Task
dans l' Explorateur d'objets et je ne pouvais pas trouver un Action<T>
paramètre comme. Il n'y a que Action
cela qui prend des void
paramètres et aucun type .
Il n'y a que 2 choses similaires: static Task Run(Action action)
et static Task Run(Func<Task> function)
mais je ne peux pas publier de paramètre (s) avec les deux.
Oui, je sais que je peux créer une méthode d'extension simple pour cela, mais ma principale question est de pouvoir l'écrire sur une seule ligne avec Task.Run()
?
c#
lambda
task-parallel-library
task
MFatihMAR
la source
la source
rawData
est un paquet de données réseau qui a une classe de conteneur (comme DataPacket) et je réutilise cette instance pour réduire la pression GC. Donc, si j'utiliserawData
directement dansTask
, il peut (probablement) être changé avant de leTask
gérer. Maintenant, je pense que je peux créer une autrebyte[]
instance pour cela. Je pense que c'est la solution la plus simple pour moi.Action<byte[]>
ne change rien à cela.Réponses:
private void RunAsync() { string param = "Hi"; Task.Run(() => MethodWithParameter(param)); } private void MethodWithParameter(string param) { //Do stuff }
Éditer
En raison de la demande générale, je dois noter que le
Task
lancement fonctionnera en parallèle avec le thread appelant. En supposant que la valeur par défaut,TaskScheduler
cela utilisera le .NETThreadPool
. Quoi qu'il en soit, cela signifie que vous devez tenir compte du ou des paramètres passés auTask
comme étant potentiellement accessibles par plusieurs threads à la fois, ce qui en fait un état partagé. Cela inclut leur accès sur le fil appelant.Dans mon code ci-dessus, ce cas est entièrement théorique. Les chaînes sont immuables. C'est pourquoi je les ai utilisés comme exemple. Mais disons que vous n'utilisez pas de
String
...Une solution consiste à utiliser
async
etawait
. Ceci, par défaut, capturera leSynchronizationContext
thread appelant et créera une continuation pour le reste de la méthode après l'appel àawait
et l'attachera au fichierTask
. Si cette méthode s'exécute sur le thread d'interface graphique WinForms, elle sera de typeWindowsFormsSynchronizationContext
.La continuation s'exécutera après avoir été publiée sur le capturé
SynchronizationContext
- encore une fois par défaut. Vous serez donc de retour sur le fil de discussion avec lequel vous avez commencé après l'await
appel. Vous pouvez modifier cela de différentes manières, notamment en utilisantConfigureAwait
. En bref, le reste de cette méthode ne se poursuivra pas tant que le ne sera pasTask
terminé sur un autre thread. Mais le thread appelant continuera à s'exécuter en parallèle, mais pas le reste de la méthode.Cette attente pour terminer l'exécution du reste de la méthode peut être souhaitable ou non. Si rien dans cette méthode n'accède plus tard aux paramètres passés à,
Task
vous ne voudrez peut-être pas du tout utiliserawait
.Ou peut-être que vous utiliserez ces paramètres beaucoup plus tard dans la méthode. Aucune raison de le faire
await
immédiatement car vous pouvez continuer à travailler en toute sécurité. N'oubliez pas que vous pouvez stocker leTask
retourné dans une variable etawait
dessus plus tard - même dans la même méthode. Par exemple, une fois que vous avez besoin d'accéder aux paramètres passés en toute sécurité après avoir effectué un tas d'autres travaux. Encore une fois, vous n'avez pas besoin deawait
le faire sur laTask
droite lorsque vous l'exécutez.Quoi qu'il en soit, un moyen simple de rendre ce thread-safe par rapport aux paramètres passés à
Task.Run
est de le faire:Vous devez d'abord décorer
RunAsync
avecasync
:private async void RunAsync()
Note importante
De préférence, la méthode marquée ne doit pas retourner void, comme le mentionne la documentation liée. L'exception courante à cela concerne les gestionnaires d'événements tels que les clics sur les boutons, etc. Ils doivent revenir nuls. Sinon, j'essaie toujours de retourner un ou lors de l'utilisation . C'est une bonne pratique pour plusieurs raisons.
async
Task
Task<TResult>
async
Vous pouvez maintenant
await
exécuter laTask
méthode ci-dessous. Vous ne pouvez pas utiliserawait
sansasync
.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
Donc, en général, si vous faites
await
la tâche, vous pouvez éviter de traiter les paramètres passés comme une ressource potentiellement partagée avec tous les pièges de la modification de quelque chose à partir de plusieurs threads à la fois. Méfiez-vous également des fermetures . Je ne les couvrirai pas en profondeur, mais l'article lié fait un excellent travail.Note d'accompagnement
Un peu hors sujet, mais soyez prudent en utilisant n'importe quel type de "blocage" sur le thread de l'interface graphique WinForms car il est marqué par
[STAThread]
. L'utilisationawait
ne bloquera pas du tout, mais je la vois parfois utilisée en conjonction avec une sorte de blocage."Bloquer" est entre guillemets car vous ne pouvez techniquement pas bloquer le thread d'interface graphique WinForms . Oui, si vous utilisez
lock
sur le fil GUI WinForms , il va encore pomper des messages, malgré vous pensant qu'il est « bloqué ». Ce n'est pas.Cela peut provoquer des problèmes bizarres dans de très rares cas. L'une des raisons pour lesquelles vous ne voulez jamais utiliser de
lock
peinture, par exemple. Mais c'est un cas marginal et complexe; cependant je l'ai vu causer des problèmes insensés. Je l'ai donc noté par souci d'exhaustivité.la source
Task.Run(() => MethodWithParameter(param));
. Ce qui signifie que s'ilparam
est modifié après leTask.Run
, vous pourriez avoir des résultats inattendus sur leMethodWithParameter
.Utilisez la capture de variables pour «transmettre» les paramètres.
var x = rawData; Task.Run(() => { // Do something with 'x' });
Vous pouvez également utiliser
rawData
directement mais vous devez faire attention, si vous changez la valeur de l'rawData
extérieur d'une tâche (par exemple un itérateur dans unefor
boucle) cela changera également la valeur à l'intérieur de la tâche.la source
Task.Run
.À partir de maintenant, vous pouvez également:
Action<int> action = (o) => Thread.Sleep(o); int param = 10; await new TaskFactory().StartNew(action, param)
la source
IDisposable
objet dans le délégué de tâche pour résoudre l'avertissement ReSharper «La variable capturée est supprimée dans la portée externe» , cela le fait très bien. Contrairement à la croyance populaire, il n'y a rien de mal à utiliserTask.Factory.StartNew
au lieu de l'Task.Run
endroit où vous devez passer l'état. Regardez ici .Je sais que c'est un vieux fil de discussion, mais je voulais partager une solution que j'ai fini par devoir utiliser car le message accepté a toujours un problème.
Le problème:
Comme l'a souligné Alexandre Severino, si
param
(dans la fonction ci-dessous) change peu de temps après l'appel de la fonction, vous pourriez avoir un comportement inattendu dansMethodWithParameter
.Ma solution:
Pour en tenir compte, j'ai fini par écrire quelque chose de plus comme la ligne de code suivante:
(new Func<T, Task>(async (p) => await Task.Run(() => MethodWithParam(p)))).Invoke(param);
Cela m'a permis d'utiliser en toute sécurité le paramètre de manière asynchrone malgré le fait que le paramètre a changé très rapidement après le démarrage de la tâche (ce qui a causé des problèmes avec la solution publiée).
En utilisant cette approche,
param
(type valeur) obtient sa valeur transmise, donc même si la méthode asynchrone s'exécute après lesparam
modifications, ellep
aura la valeur queparam
j'avais lorsque cette ligne de code a été exécutée.la source
var localParam = param; await Task.Run(() => MethodWithParam(localParam));
Utilisez simplement Task.Run
var task = Task.Run(() => { //this will already share scope with rawData, no need to use a placeholder });
Ou, si vous souhaitez l'utiliser dans une méthode et attendre la tâche plus tard
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; }
la source
for(int rawData = 0; rawData < 10; ++rawData) { Task.Run(() => { Console.WriteLine(rawData); } ) }
elles ne se comporteront pas comme si elles avaientrawData
été transmises comme dans l'exemple StartNew de l'OP.On ne sait pas si le problème d'origine était le même problème que moi: vouloir maximiser les threads du processeur sur le calcul à l'intérieur d'une boucle tout en préservant la valeur de l'itérateur et en restant en ligne pour éviter de passer une tonne de variables à une fonction de travail.
for (int i = 0; i < 300; i++) { Task.Run(() => { var x = ComputeStuff(datavector, i); // value of i was incorrect var y = ComputeMoreStuff(x); // ... }); }
J'ai fait fonctionner cela en changeant l'itérateur externe et en localisant sa valeur avec une porte.
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(); }
la source
L'idée est d'éviter d'utiliser un signal comme ci-dessus. Le pompage de valeurs int dans une structure empêche ces valeurs de changer (dans la structure). J'ai eu le problème suivant: loop var je changerais avant l'appel de DoSomething (i) (j'ai été incrémenté à la fin de la boucle avant () => DoSomething (i, i i)). Avec les structures, cela n'arrive plus. Nasty bug à trouver: DoSomething (i, i i) a l'air génial, mais jamais sûr s'il est appelé à chaque fois avec une valeur différente pour i (ou juste 100 fois avec i = 100), d'où -> 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)); }
la source