Je travaille sur un projet de réseau multi-tâches et je suis nouveau sur Threading.Tasks
. J’ai implémenté un simple Task.Factory.StartNew()
et je me demande comment je peux le faire avec Task.Run()
?
Voici le code de base:
Task.Factory.StartNew(new Action( (x) => { // Do something with 'x' }), rawData);
J’ai regardé dans System.Threading.Tasks.Task
dans l’ Explorateur d’objects et je n’ai pas trouvé de paramètre Action
. Il n’y a qu’une Action
qui prend un paramètre void
et aucun type .
Il y a seulement 2 choses similaires: exécution de static Task Run(Action action)
et static Task Run(Func function)
mais impossible de publier des parameters avec les deux.
Oui, je sais que je peux créer une méthode d’extension simple, mais ma principale question est de savoir si nous pouvons l’écrire sur une seule ligne avec Task.Run()
?
private void RunAsync() { ssortingng param = "Hi"; Task.Run(() => MethodWithParameter(param)); } private void MethodWithParameter(ssortingng param) { //Do stuff }
modifier
En raison d’une demande populaire, je dois noter que la Task
lancée sera exécutée en parallèle avec le thread appelant. En supposant le TaskScheduler
par défaut, il utilisera le ThreadPool
.NET. Quoi qu’il en soit, cela signifie que vous devez tenir compte du fait que le ou les parameters transmis à la Task
sont potentiellement accessibles par plusieurs threads à la fois, ce qui les rend en état partagé. Cela inclut leur access sur le thread appelant.
Dans mon code ci-dessus, ce cas est entièrement résolu. Les chaînes sont immuables. C’est pourquoi je les ai utilisés comme exemple. Mais disons que vous n’utilisez pas de Ssortingng
…
Une solution consiste à utiliser async
et à await
. Cela, par défaut, capturera le SynchronizationContext
du thread appelant et créera une suite pour le rest de la méthode après l’appel à await
et à l’attacher à la Task
créée. Si cette méthode est en cours d’exécution sur le thread d’interface graphique WinForms, elle sera de type WindowsFormsSynchronizationContext
.
La suite sera exécutée après avoir été renvoyée sur le SynchronizationContext
capturé – encore une fois uniquement par défaut. Donc, vous serez de retour sur le fil avec lequel vous avez commencé après l’appel en await
. Vous pouvez changer cela de différentes manières, notamment en utilisant ConfigureAwait
. En bref, le rest de cette méthode ne continuera pas tant que la Task
ne sera pas terminée sur un autre thread. Mais le thread appelant continuera à fonctionner en parallèle, mais pas le rest de la méthode.
Cette attente pour terminer l’exécution du rest de la méthode peut ou non être souhaitable. Si rien dans cette méthode n’accède ultérieurement aux parameters transmis à la Task
vous ne souhaiterez peut-être pas utiliser l’ await
.
Ou peut-être vous utilisez ces parameters beaucoup plus tard dans la méthode. Aucune raison d’ await
immédiatement car vous pouvez continuer à travailler en toute sécurité. N’oubliez pas que vous pouvez stocker la Task
renvoyée dans une variable et l’ await
plus tard, même dans la même méthode. Par exemple, une fois que vous avez besoin d’accéder aux parameters passés en toute sécurité après avoir fait un tas d’autres travaux. Encore une fois, vous n’avez pas besoin d’ await
la Task
correctement lorsque vous l’exécutez.
Quoi qu’il en soit, un moyen simple de rendre ce thread sûr pour les parameters passés à Task.Run
est de le faire:
Vous devez d’abord décorer RunAsync
avec async
:
private async void RunAsync()
Note importante
De préférence, la méthode marquée async
ne doit pas être annulée, comme le mentionne la documentation liée. L’exception commune à ceci est les gestionnaires d’événements tels que les clics sur les boutons et autres. Ils doivent retourner nuls. Sinon, j’essaie toujours de renvoyer une Task
ou une Task
lors de l’utilisation async
. C’est une bonne pratique pour plusieurs raisons.
Maintenant, vous pouvez await
exécution de la Task
comme ci-dessous. Vous ne pouvez pas utiliser await
sans 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
Donc, en général, si vous await
la tâche, vous pouvez éviter de traiter les parameters passés comme une ressource potentiellement partagée avec tous les pièges de la modification d’un élément de plusieurs threads à la fois. Aussi, méfiez-vous des fermetures . Je ne couvrirai pas ces questions en profondeur, mais l’article lié en fait un excellent travail.
Note latérale
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é avec [STAThread]
. Utiliser await
ne bloquera pas du tout, mais je le vois parfois utilisé conjointement avec une sorte de blocage.
“Bloquer” est entre guillemets car vous ne pouvez pas bloquer techniquement le thread de l’interface graphique WinForms . Oui, si vous utilisez lock
sur le thread d’interface graphique de WinForms, les messages continueront à être diffusés, même si vous pensez que cela est “bloqué”. Ce n’est pas.
Cela peut causer des problèmes bizarres dans de très rares cas. Une des raisons pour lesquelles vous ne voulez jamais utiliser un lock
lors de la peinture, par exemple. Mais c’est une affaire complexe et à la limite; Cependant, je l’ai vu causer des problèmes fous. Je l’ai donc noté pour être complet.
Utilisez la capture de variable pour “transmettre” les parameters.
var x = rawData; Task.Run(() => { // Do something with 'x' });
Vous pouvez également utiliser directement rawData
mais vous devez faire attention, si vous modifiez la valeur de rawData
dehors d’une tâche (par exemple, un iterator dans une boucle for
), la valeur à l’intérieur de la tâche sera également modifiée.
Je sais que c’est un vieux sujet, mais je voulais partager une solution que j’ai dû utiliser puisque le message accepté a toujours un problème.
Le problème:
Comme le souligne Alexandre Severino, si param
(dans la fonction ci-dessous) change peu après l’appel de la fonction, vous pourriez avoir un comportement inattendu dans MethodWithParameter
.
Task.Run(() => MethodWithParameter(param));
Ma solution:
Pour en tenir compte, j’ai fini par écrire quelque chose de plus comme la ligne de code suivante:
(new Func(async (p) => await Task.Run(() => MethodWithParam(p)))).Invoke(param);
Cela m’a permis d’utiliser le paramètre de manière asynchrone en toute sécurité 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 de valeur) obtient sa valeur, donc même si la méthode asynchrone s’exécute après les changements de param
, p
aura la valeur que param
avait lorsque cette ligne de code était exécutée.
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 SomethingAsync () { 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; }
A partir de maintenant, vous pouvez aussi:
Action action = (o) => Thread.Sleep(o); int param = 10; await new TaskFactory().StartNew(action, param)