Le moyen le plus simple de faire un feu et d'oublier la méthode en C #?

150

J'ai vu dans WCF qu'ils ont l' [OperationContract(IsOneWay = true)]attribut. Mais WCF semble un peu lent et lourd juste pour créer une fonction non bloquante. Idéalement, il y aurait quelque chose comme un vide statique non bloquant MethodFoo(){}, mais je ne pense pas que cela existe.

Quel est le moyen le plus rapide de créer un appel de méthode non bloquant en C #?

Par exemple

class Foo
{
    static void Main()
    {
        FireAway(); //No callback, just go away
        Console.WriteLine("Happens immediately");
    }

    static void FireAway()
    {
        System.Threading.Thread.Sleep(5000);
        Console.WriteLine("5 seconds later");
    }
}

NB : Tous ceux qui liront ceci devraient se demander s'ils veulent réellement que la méthode se termine. (Voir la réponse principale n ° 2) Si la méthode doit se terminer, dans certains endroits, comme une application ASP.NET, vous devrez faire quelque chose pour bloquer et maintenir le thread en vie. Sinon, cela pourrait conduire à "feu-oubliez-mais-ne-exécutez jamais-réellement", auquel cas, bien sûr, il serait plus simple d'écrire aucun code. ( Une bonne description de la façon dont cela fonctionne dans ASP.NET )

MatthewMartin
la source

Réponses:

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

(cinq ans plus tard...)

Task.Run(() => FireAway());

comme l'a souligné luisperezphd .


la source
Vous gagnez. Non, vraiment, cela semble être la version la plus courte, à moins que vous ne l'encapsuliez dans une autre fonction.
OregonGhost
13
En y réfléchissant ... dans la plupart des applications, c'est très bien, mais dans la vôtre, l'application console se fermera avant que FireAway n'envoie quoi que ce soit à la console. Les threads ThreadPool sont des threads d'arrière-plan et meurent lorsque l'application meurt. D'un autre côté, l'utilisation d'un Thread ne fonctionnerait pas non plus car la fenêtre de la console disparaîtrait avant que FireAway n'essaye d'écrire dans la fenêtre.
3
Il n'y a aucun moyen d'avoir un appel de méthode non bloquant dont l'exécution est garantie, c'est donc en fait la réponse la plus précise à la question IMO. Si vous avez besoin de garantir l'exécution, un blocage potentiel doit être introduit via une structure de contrôle telle que AutoResetEvent (comme Kev l'a mentionné)
Guvante
1
Pour exécuter la tâche dans un thread indépendant du pool de threads, je pense que vous pouvez également aller(new Action(FireAway)).BeginInvoke()
Sam Harwell
2
Qu'en est-il Task.Factory.StartNew(() => myevent());de la réponse stackoverflow.com/questions/14858261/…
Luis Perez
39

Pour C # 4.0 et plus récent, il me semble que la meilleure réponse est maintenant donnée ici par Ade Miller: Le moyen le plus simple de faire un feu et d'oublier la méthode en c # 4.0

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

Ou même...

Task.Factory.StartNew(FireAway);

Ou...

new Task(FireAway).Start();

FireAwayest

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

Donc, en raison de la concordance des noms de classe et de méthode, cela bat la version de threadpool de six à dix-neuf caractères selon celui que vous choisissez :)

ThreadPool.QueueUserWorkItem(o => FireAway());
Patrick Szalapski
la source
11
Je suis très intéressé à avoir les meilleures réponses facilement disponibles sur les bonnes questions. J'encouragerais certainement les autres à faire de même.
Patrick Szalapski
3
Selon stackoverflow.com/help/referencing , vous devez utiliser des blockquotes pour indiquer que vous citez d'une autre source. Comme c'est le cas, votre réponse semble être tout votre travail. Votre intention en republiant une copie exacte de la réponse aurait pu être réalisée avec un lien dans un commentaire.
tom redfern
1
comment passer des paramètres à FireAway?
GuidoG
Je crois que vous auriez à utiliser la première solution: Task.Factory.StartNew(() => FireAway(foo));.
Patrick Szalapski
1
soyez prudent lorsque vous utilisez Task.Factory.StartNew(() => FireAway(foo));foo ne peut pas être modifié en boucle comme expliqué ici et ici
AaA
22

Pour .NET 4.5:

Task.Run(() => FireAway());
David Murdoch
la source
15
Fyi, c'est principalement court pour Task.Factory.StartNew(() => FireAway(), CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);selon blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx
Jim Geurts
2
Bon à savoir, @JimGeurts!
David Murdoch
Dans l'intérêt de la postérité, l'article lié à @JimGeurts dans leur commentaire est désormais 404'd (merci, MS!). Compte tenu de l'horodatage de cette URL, cet article de Stephen Toub semble être le bon - devblogs.microsoft.com/pfxteam
Dan Atkinson
1
@DanAtkinson vous avez raison. Voici l'original sur archive.org, juste au cas où MS le déplacerait à nouveau: web.archive.org/web/20160226022029/http://blogs.msdn.com/b/…
David Murdoch
18

Pour ajouter à la réponse de Will , s'il s'agit d'une application console, lancez simplement un AutoResetEventet un WaitHandlepour l'empêcher de se terminer avant la fin du thread de travail:

Using System;
Using System.Threading;

class Foo
{
    static AutoResetEvent autoEvent = new AutoResetEvent(false);

    static void Main()
    {
        ThreadPoolQueueUserWorkItem(new WaitCallback(FireAway), autoEvent);
        autoEvent.WaitOne(); // Will wait for thread to complete
    }

    static void FireAway(object stateInfo)
    {
        System.Threading.Thread.Sleep(5000);
        Console.WriteLine("5 seconds later");
        ((AutoResetEvent)stateInfo).Set();
    }
}
Kev
la source
s'il ne s'agit pas d'une application console et que ma classe C # est COM Visible, votre AutoEvent fonctionnera-t-il? Est-ce que autoEvent.WaitOne () bloque?
dan_l
5
@dan_l - Aucune idée, pourquoi ne pas poser cela comme une nouvelle question et faire référence à celle-ci.
Kev
@dan_l, yes WaitOneest toujours bloquant et AutoResetEventfonctionnera toujours
AaA
AutoResetEvent ne fonctionne vraiment ici que s'il existe un seul thread d'arrière-plan. Je me demande si une barrière serait meilleure lorsque plusieurs threads sont utilisés. docs.microsoft.com/en-us/dotnet/standard/threading/barrier
Martin Brown
@MartinBrown - pas sûr que ce soit vrai. Vous pouvez avoir un tableau de WaitHandles, chacun avec son propre AutoResetEventet un correspondant ThreadPool.QueueUserWorkItem. Après cela juste WaitHandle.WaitAll(arrayOfWaitHandles).
Kev
15

Un moyen simple consiste à créer et démarrer un thread avec lambda sans paramètre:

(new Thread(() => { 
    FireAway(); 
    MessageBox.Show("FireAway Finished!"); 
}) { 
    Name = "Long Running Work Thread (FireAway Call)",
    Priority = ThreadPriority.BelowNormal 
}).Start();

En utilisant cette méthode sur ThreadPool.QueueUserWorkItem, vous pouvez nommer votre nouveau thread pour faciliter le débogage. N'oubliez pas non plus d'utiliser une gestion complète des erreurs dans votre routine car toute exception non gérée en dehors d'un débogueur plantera brusquement votre application:

entrez la description de l'image ici

Robert Venables
la source
+1 lorsque vous avez besoin d'un thread dédié en raison d'un processus de longue durée ou de blocage.
Paul Turner
12

La manière recommandée de le faire lorsque vous utilisez Asp.Net et .Net 4.5.2 est d'utiliser QueueBackgroundWorkItem. Voici une classe d'assistance:

public static class BackgroundTaskRunner
{     
    public static void FireAndForgetTask(Action action)
    {
        HostingEnvironment.QueueBackgroundWorkItem(cancellationToken => // .Net 4.5.2 required
        {
            try
            {
                action();
            }
            catch (Exception e)
            {
                // TODO: handle exception
            }
        });
    }

    /// <summary>
    /// Using async
    /// </summary>
    public static void FireAndForgetTask(Func<Task> action)
    {
        HostingEnvironment.QueueBackgroundWorkItem(async cancellationToken => // .Net 4.5.2 required
        {
            try
            {
                await action();
            }
            catch (Exception e)
            {
                // TODO: handle exception
            }
        });
    }
}

Exemple d'utilisation:

BackgroundTaskRunner.FireAndForgetTask(() =>
{
    FireAway();
});

ou en utilisant async:

BackgroundTaskRunner.FireAndForgetTask(async () =>
{
    await FireAway();
});

Cela fonctionne très bien sur les sites Web Azure.

Référence: Utilisation de QueueBackgroundWorkItem pour planifier des travaux en arrière-plan à partir d'une application ASP.NET dans .NET 4.5.2

Augusto Barreto
la source
7

Appeler beginInvoke et ne pas attraper EndInvoke n'est pas une bonne approche. La réponse est simple: la raison pour laquelle vous devez appeler EndInvoke est que les résultats de l'appel (même s'il n'y a pas de valeur de retour) doivent être mis en cache par .NET jusqu'à ce que EndInvoke soit appelé. Par exemple, si le code appelé lève une exception, l'exception est mise en cache dans les données d'appel. Jusqu'à ce que vous appeliez EndInvoke, il reste en mémoire. Après avoir appelé EndInvoke, la mémoire peut être libérée. Dans ce cas particulier, il est possible que la mémoire reste jusqu'à ce que le processus s'arrête car les données sont conservées en interne par le code d'appel. Je suppose que le GC pourrait éventuellement les collecter, mais je ne sais pas comment le GC saurait que vous avez abandonné les données plutôt que de simplement prendre beaucoup de temps pour les récupérer. J'en doute. Par conséquent, une fuite de mémoire peut se produire.

Plus d'informations peuvent être trouvées sur http://haacked.com/archive/2009/01/09/asynchronous-fire-and-forget-with-lambdas.aspx

Manoj Aggarwal
la source
3
Le GC peut dire quand des choses sont abandonnées s'il n'y a aucune référence à elles. Si BeginInvokeretourne un objet wrapper qui contient une référence à, mais n'est pas référencé par, l'objet qui contient les données de résultat réelles, l'objet wrapper deviendrait éligible pour la finalisation une fois que toutes les références à celui-ci ont été abandonnées.
supercat
Je remets en question en disant que ce n'est "pas une bonne approche"; testez-le, avec les conditions d'erreur qui vous préoccupent, et voyez si vous rencontrez des problèmes. Même si le garbage collection n'est pas parfait, il n'affectera probablement pas sensiblement la plupart des applications. Selon Microsoft, "Vous pouvez appeler EndInvoke pour récupérer la valeur de retour du délégué, si nécessaire, mais ce n'est pas obligatoire. EndInvoke bloquera jusqu'à ce que la valeur de retour puisse être récupérée." de: msdn.microsoft.com/en-us/library/0b1bf3y3(v=vs.90).aspx
Abacus
5

Près de 10 ans plus tard:

Task.Run(FireAway);

J'ajouterais la gestion des exceptions et la journalisation dans FireAway

Oscar Fraxedas
la source
3

L'approche la plus simple .NET 2.0 et versions ultérieures utilise le modèle de programmation asynchrone (c'est-à-dire BeginInvoke sur un délégué):

static void Main(string[] args)
{
      new MethodInvoker(FireAway).BeginInvoke(null, null);

      Console.WriteLine("Main: " + Thread.CurrentThread.ManagedThreadId);

      Thread.Sleep(5000);
}

private static void FireAway()
{
    Thread.Sleep(2000);

    Console.WriteLine("FireAway: " + Thread.CurrentThread.ManagedThreadId );  
}
Cendre
la source
Avant d' Task.Run()exister, c'était probablement la manière la plus concise de le faire.
binki