Méthode la plus simple pour faire un feu et oublier la méthode dans c # 4.0

J’aime beaucoup cette question:

La manière la plus simple de faire un feu et d’oublier la méthode en C #?

Je veux juste savoir que maintenant que nous avons des extensions Parallel en C # 4.0, y a-t-il un moyen plus propre de faire Fire & Forget avec Parallel linq?

Avec la classe Task , oui, mais PLINQ est vraiment destiné à interroger des collections.

Quelque chose comme le suivant le fera avec la tâche.

 Task.Factory.StartNew(() => FireAway()); 

Ou même…

 Task.Factory.StartNew(FireAway); 

Ou…

 new Task(FireAway).Start(); 

FireAway est

 public static void FireAway() { // Blah... } 

Donc, en raison de la complexité des noms de classe et de méthode, cela bat la version de pool de threads de six à dix-neuf caractères en fonction de celle choisie 🙂

 ThreadPool.QueueUserWorkItem(o => FireAway()); 

Pas une réponse pour 4.0, mais notez que dans .Net 4.5 vous pouvez rendre cela encore plus simple avec:

 #pragma warning disable 4014 Task.Run(() => { MyFireAndForgetMethod(); }).ConfigureAwait(false); #pragma warning restore 4014 

Le pragma est de désactiver l’avertissement qui vous indique que vous exécutez cette tâche en tant qu’incendie et en cas d’oubli.

Si la méthode entre accolades renvoie une tâche:

 #pragma warning disable 4014 Task.Run(async () => { await MyFireAndForgetMethod(); }).ConfigureAwait(false); #pragma warning restore 4014 

Disons ça:

Task.Run renvoie une tâche qui génère un avertissement du compilateur (avertissement CS4014) en notant que ce code sera exécuté en arrière-plan – c’est exactement ce que vous vouliez, nous désactivons donc l’avertissement 4014.

Par défaut, les tâches tentent de “rassembler les données sur le thread d’origine”, ce qui signifie que cette tâche s’exécutera en arrière-plan, puis tentera de revenir au thread qui l’a démarrée. Souvent, tirez et oubliez les tâches qui se terminent une fois le thread d’origine terminé. Cela provoquera une exception ThreadAbortException. Dans la plupart des cas, c’est inoffensif – c’est juste pour vous dire que j’ai essayé de rejoindre, j’ai échoué, mais vous ne vous en fichez plus. Mais ThreadAbortExceptions est toujours un peu bruyant dans vos journaux de production ou dans votre débogueur en dev local. .ConfigureAwait(false) est juste un moyen de restr net et de dire explicitement, lancez ceci en arrière-plan, et c’est tout.

Étant donné que c’est verbeux, en particulier le pragma laid, j’utilise une méthode de bibliothèque pour cela:

 public static class TaskHelper { ///  /// Runs a TPL Task fire-and-forget style, the right way - in the /// background, separate from the current thread, with no risk /// of it trying to rejoin the current thread. ///  public static void RunBg(Func fn) { Task.Run(fn).ConfigureAwait(false); } ///  /// Runs a task fire-and-forget style and notifies the TPL that this /// will not need a Thread to resume on for a long time, or that there /// are multiple gaps in thread use that may be long. /// Use for example when talking to a slow webservice. ///  public static void RunBgLong(Func fn) { Task.Factory.StartNew(fn, TaskCreationOptions.LongRunning) .ConfigureAwait(false); } } 

Usage:

 TaskHelper.RunBg(async () => { await doSomethingAsync(); } 

J’ai quelques problèmes avec la principale réponse à cette question.

Tout d’abord, dans une situation d’ incendie et d’oubli , vous await probablement pas la tâche, il est donc inutile d’append ConfigureAwait(false) . Si vous n’attendez pas la valeur renvoyée par ConfigureAwait , cela ne peut avoir aucun effet.

Deuxièmement, vous devez savoir ce qui se passe lorsque la tâche se termine avec une exception. Considérons la solution simple suggérée par @ ade-miller:

 Task.Factory.StartNew(SomeMethod); // .NET 4.0 Task.Run(SomeMethod); // .NET 4.5 

Cela présente un danger: si une exception non SomeMethod() s’échappe de SomeMethod() , cette exception ne sera jamais observée et peut être relancée sur le thread du finaliseur, provoquant le blocage de votre application. Je recommande donc d’utiliser une méthode d’assistance pour s’assurer que toutes les exceptions qui en résultent sont observées.

Vous pourriez écrire quelque chose comme ceci:

 public static class Blindly { private static readonly Action DefaultErrorContinuation = t => { try { t.Wait(); } catch {} }; public static void Run(Action action, Action handler = null) { if (action == null) throw new ArgumentNullException(nameof(action)); var task = Task.Run(action); // Adapt as necessary for .NET 4.0. if (handler == null) { task.ContinueWith( DefaultErrorContinuation, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnFaulted); } else { task.ContinueWith( t => handler(t.Exception.GetBaseException()), TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnFaulted); } } } 

Cette implémentation doit avoir une surcharge minimale: la continuation n’est invoquée que si la tâche ne se termine pas correctement et doit être appelée de manière synchrone (par opposition à la planification séparée de la tâche d’origine). Dans le cas des “paresseux”, vous ne subirez même pas d’allocation pour le délégué de continuation.

Lancer une opération asynchrone devient alors sortingvial:

 Blindly.Run(SomeMethod); // Ignore error Blindly.Run(SomeMethod, e => Log.Warn("Whoops", e)); // Log error 

1. C’était le comportement par défaut dans .NET 4.0. Dans .NET 4.5, le comportement par défaut a été modifié de sorte que les exceptions non observées ne soient pas rediffusées sur le thread du finaliseur (bien que vous puissiez toujours les observer via l’événement UnobservedTaskException sur TaskScheduler). Cependant, la configuration par défaut peut être remplacée et même si votre application nécessite .NET 4.5, vous ne devez pas supposer que les exceptions de tâches non observées seront inoffensives.

Juste pour résoudre un problème qui se produira avec la réponse de Mike Strobel:

Si vous utilisez var task = Task.Run(action) et que vous affectez une suite à cette tâche, vous risquez d’exécuter une exception avant d’affecter une suite de gestionnaire d’exception à la Task . Donc, la classe ci-dessous devrait être libre de ce risque:

 using System; using System.Threading.Tasks; namespace MyNameSpace { public sealed class AsyncManager : IAsyncManager { private Action DefaultExeptionHandler = t => { try { t.Wait(); } catch { /* Swallow the exception */ } }; public Task Run(Action action, Action exceptionHandler = null) { if (action == null) { throw new ArgumentNullException(nameof(action)); } var task = new Task(action); Action handler = exceptionHandler != null ? new Action(t => exceptionHandler(t.Exception.GetBaseException())) : DefaultExeptionHandler; var continuation = task.ContinueWith(handler, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnFaulted); task.Start(); return continuation; } } } 

Ici, la task n’est pas exécutée directement, elle est créée, une continuation est affectée et alors seulement la tâche est exécutée pour éliminer le risque que la tâche termine l’exécution (ou lance une exception) avant d’affecter une suite.

La méthode Run ici renvoie la Task continuation, donc je suis capable d’écrire des tests unitaires en s’assurant que l’exécution est terminée. Vous pouvez toutefois l’ignorer en toute sécurité lors de votre utilisation.