Task.Run avec paramètre (s)?

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)