web-dev-qa-db-fra.com

Manière la plus simple de faire un feu et d'oublier la méthode en c # 4.0

J'aime vraiment cette question:

La façon 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 parallèles en C # 4.0, y a-t-il une meilleure façon plus propre de faire Fire & Forget avec Parallel linq?

81
Jonathon Kresner

Pas une réponse pour 4.0, mais il convient de noter 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 comme un incendie et oubliez.

Si la méthode à l'intérieur des accolades renvoie une tâche:

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

Décomposons cela:

Task.Run renvoie une tâche, qui génère un avertissement du compilateur (avertissement CS4014) 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 "marshaler 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. Tirez souvent et oubliez que les tâches se terminent une fois le thread d'origine terminé. Cela provoquera une exception ThreadAbortException. Dans la plupart des cas, cela est inoffensif - cela vous dit simplement que j'ai essayé de rejoindre, j'ai échoué, mais vous ne vous en souciez pas de toute façon. Mais il est toujours un peu bruyant d'avoir ThreadAbortExceptions soit dans vos journaux dans Production, soit dans votre débogueur dans le développement local. .ConfigureAwait(false) est juste un moyen de rester en ordre et de dire explicitement, exécutez cela en arrière-plan, et c'est tout.

Puisque c'est verbeux, en particulier le vilain pragma, j'utilise une méthode de bibliothèque pour cela:

public static class TaskHelper
{
    /// <summary>
    /// 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.
    /// </summary>
    public static void RunBg(Func<Task> fn)
    {
        Task.Run(fn).ConfigureAwait(false);
    }

    /// <summary>
    /// 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.
    /// </summary>
    public static void RunBgLong(Func<Task> fn)
    {
        Task.Factory.StartNew(fn, TaskCreationOptions.LongRunning)
            .ConfigureAwait(false);
    }
}

Usage:

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

Avec la classe Task oui, mais PLINQ est vraiment pour interroger des collections.

Quelque chose comme le suivant le fera avec Task.

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 concision des noms de classe et de méthode, cela dépasse la version de threadpool de six à dix-neuf caractères selon celui que vous choisissez :)

ThreadPool.QueueUserWorkItem(o => FireAway());
83
Ade Miller

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

Tout d'abord, dans une vraie situation feu-et-oublier, vous n'aurez probablement pas await la tâche, il est donc inutile d'ajouter ConfigureAwait(false). Si vous n'avez pas await 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érez 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 gérée s'échappe de SomeMethod(), cette exception ne sera jamais observée et peut1 être renvoyé sur le thread du finaliseur, faisant planter votre application. Je recommanderais donc d'utiliser une méthode d'assistance pour garantir que toutes les exceptions qui en résultent sont respectées.

Vous pourriez écrire quelque chose comme ceci:

public static class Blindly
{
    private static readonly Action<Task> DefaultErrorContinuation =
        t =>
        {
            try { t.Wait(); }
            catch {}
        };

    public static void Run(Action action, Action<Exception> 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 elle doit être invoquée de manière synchrone (par opposition à être planifiée séparément de la tâche d'origine). Dans le cas "paresseux", vous n'encourrez même pas d'allocation pour le délégué de continuation.

Lancer une opération asynchrone devient alors trivial:

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 telle sorte que les exceptions non observées seraient pas renvoyé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.

24
Mike Strobel

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 après avoir assigné une continuation à cette tâche, vous courrez le risque que Task lève une exception avant d'affecter une continuation de gestionnaire d'exceptions à Task. Ainsi, la classe ci-dessous devrait être exempte de ce risque:

using System;
using System.Threading.Tasks;

namespace MyNameSpace
{
    public sealed class AsyncManager : IAsyncManager
    {
        private Action<Task> DefaultExeptionHandler = t =>
        {
            try { t.Wait(); }
            catch { /* Swallow the exception */ }
        };

        public Task Run(Action action, Action<Exception> exceptionHandler = null)
        {
            if (action == null) { throw new ArgumentNullException(nameof(action)); }

            var task = new Task(action);

            Action<Task> handler = exceptionHandler != null ?
                new Action<Task>(t => exceptionHandler(t.Exception.GetBaseException())) :
                DefaultExeptionHandler;

            var continuation = task.ContinueWith(handler,
                TaskContinuationOptions.ExecuteSynchronously
                | TaskContinuationOptions.OnlyOnFaulted);
            task.Start();

            return continuation;
        }
    }
}

Ici, le task n'est pas exécuté directement, au lieu de cela il est créé, 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 lève une exception) avant d'affecter un continuation.

La méthode Run renvoie ici la suite Task donc je peux écrire des tests unitaires en m'assurant que l'exécution est terminée. Vous pouvez cependant l'ignorer en toute sécurité dans votre utilisation.

6
Mert Akcakaya