¿La manera más simple de hacer fuego y olvidar el método en C #?

150

Vi en WCF que tienen el [OperationContract(IsOneWay = true)]atributo. Pero WCF parece un poco lento y pesado solo para crear una función sin bloqueo. Idealmente, habría algo como no bloqueo de vacío estático MethodFoo(){}, pero no creo que exista.

¿Cuál es la forma más rápida de crear una llamada a un método sin bloqueo en C #?

P.ej

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 : Todos los que lean esto deberían pensar si realmente quieren que el método termine. (Consulte la respuesta principal n. ° 2) Si el método tiene que finalizar, en algunos lugares, como una aplicación ASP.NET, deberá hacer algo para bloquear y mantener vivo el hilo. De lo contrario, esto podría llevar a "disparar-olvidar-pero-nunca-realmente-ejecutar", en cuyo caso, por supuesto, sería más simple no escribir ningún código. ( Una buena descripción de cómo funciona esto en ASP.NET )

MatthewMartin
fuente

Respuestas:

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

(cinco años después...)

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

como lo señaló luisperezphd .


fuente
Tú ganas. No, realmente, esta parece ser la versión más corta, a menos que la encapsules en otra función.
OregonGhost
13
Pensando en esto ... en la mayoría de las aplicaciones esto está bien, pero en la suya la aplicación de consola se cerrará antes de que FireAway envíe algo a la consola. Los hilos de ThreadPool son hilos de fondo y mueren cuando la aplicación muere. Por otro lado, usar un Thread tampoco funcionaría ya que la ventana de la consola desaparecería antes de que FireAway intentara escribir en la ventana.
3
No hay forma de tener una llamada al método sin bloqueo que se garantice su ejecución, por lo que esta es la respuesta más precisa a la pregunta IMO. Si necesita la ejecución de garantía a continuación, el potencial de bloqueo tiene que ser introducido a través de una estructura de control como AutoResetEvent (como se ha mencionado Kev)
Guvante
1
Para ejecutar la tarea en un hilo independiente del grupo de subprocesos, creo que también se puede ir(new Action(FireAway)).BeginInvoke()
Sam Harwell
2
¿Qué pasa con esto Task.Factory.StartNew(() => myevent());de la respuesta stackoverflow.com/questions/14858261/…
Luis Perez
39

Para C # 4.0 y más reciente, me parece que la mejor respuesta ahora la da Ade Miller: la forma más sencilla de hacer fuego y olvidar el método en c # 4.0

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

O incluso...

Task.Factory.StartNew(FireAway);

O...

new Task(FireAway).Start();

Donde FireAwayesta

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

Entonces, en virtud de la brevedad del nombre de la clase y el método, esto supera a la versión del conjunto de hilos entre seis y diecinueve caracteres, dependiendo del que elija :)

ThreadPool.QueueUserWorkItem(o => FireAway());
Patrick Szalapski
fuente
11
Estoy más interesado en tener las mejores respuestas fácilmente disponibles en buenas preguntas. Definitivamente alentaría a otros a hacer lo mismo.
Patrick Szalapski
3
De acuerdo con stackoverflow.com/help/referencing , se le requiere que use blockquotes para indicar que está cotizando desde otra fuente. Como es su respuesta parece ser todo su propio trabajo. Su intención al volver a publicar una copia exacta de la respuesta podría haberse logrado con un enlace en un comentario.
Tom Redfern
1
¿Cómo pasar parámetros a FireAway?
GuidoG
Creo que tendría que utilizar la primera solución: Task.Factory.StartNew(() => FireAway(foo));.
Patrick Szalapski
1
tenga cuidado al usar Task.Factory.StartNew(() => FireAway(foo));foo no se puede modificar en bucle como se explica aquí y aquí
AaA
22

Para .NET 4.5:

Task.Run(() => FireAway());
David Murdoch
fuente
15
Fyi, esto es principalmente corto de Task.Factory.StartNew(() => FireAway(), CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);acuerdo con blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx
Jim Geurts
2
¡Es bueno saberlo, @JimGeurts!
David Murdoch el
En interés de la posteridad, el artículo vinculado a ser @JimGeurts en su comentario ahora tiene 404 (¡gracias, MS!). Dado el sello de fecha en esa url, este artículo de Stephen Toub parece ser el correcto - devblogs.microsoft.com/pfxteam/…
Dan Atkinson
1
@DanAtkinson tienes razón. Aquí está el original en archive.org, por si MS lo mueve de nuevo: web.archive.org/web/20160226022029/http://blogs.msdn.com/b/…
David Murdoch
18

Para agregar a la respuesta de Will , si esta es una aplicación de consola, simplemente agregue una AutoResetEventy una WaitHandlepara evitar que salga antes de que se complete el subproceso de trabajo:

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
fuente
Si esta no es una aplicación de consola y mi clase C # es COM Visible, ¿funcionará su AutoEvent? ¿AutoEvent.WaitOne () está bloqueando?
dan_l
55
@dan_l - No tengo idea, ¿por qué no hacer eso como una nueva pregunta y hacer referencia a esta?
Kev
@dan_l, sí WaitOnesiempre está bloqueando y AutoResetEventsiempre funcionará
AaA
AutoResetEvent solo funciona realmente aquí si hay un solo hilo de fondo. Me pregunto si una barrera sería mejor cuando se usan múltiples hilos. docs.microsoft.com/en-us/dotnet/standard/threading/barrier
Martin Brown
@ MartinBrown: no estoy seguro de que sea cierto. Puede tener una matriz de WaitHandles, cada uno con su propio AutoResetEventy un correspondiente ThreadPool.QueueUserWorkItem. Después de eso solo WaitHandle.WaitAll(arrayOfWaitHandles).
Kev
15

Una manera fácil es crear e iniciar un hilo con lambda sin parámetros:

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

Al usar este método sobre ThreadPool.QueueUserWorkItem, puede nombrar su nuevo hilo para facilitar la depuración. Además, no olvide utilizar el manejo extensivo de errores en su rutina porque cualquier excepción no manejada fuera de un depurador bloqueará abruptamente su aplicación:

ingrese la descripción de la imagen aquí

Robert Venables
fuente
+1 para cuando necesita un subproceso dedicado debido a un proceso de bloqueo o de larga ejecución.
Paul Turner
12

La forma recomendada de hacer esto cuando usa Asp.Net y .Net 4.5.2 es mediante QueueBackgroundWorkItem. Aquí hay una clase auxiliar:

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
            }
        });
    }
}

Ejemplo de uso:

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

o usando asíncrono:

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

Esto funciona muy bien en los sitios web de Azure.

Referencia: Uso de QueueBackgroundWorkItem para programar trabajos en segundo plano desde una aplicación ASP.NET en .NET 4.5.2

Augusto Barreto
fuente
7

Llamar a beginInvoke y no capturar EndInvoke no es un buen enfoque. La respuesta es simple: la razón por la que debe llamar a EndInvoke es porque los resultados de la invocación (incluso si no hay un valor de retorno) deben ser almacenados en caché por .NET hasta que se llame a EndInvoke. Por ejemplo, si el código invocado genera una excepción, la excepción se almacena en caché en los datos de invocación. Hasta que llame a EndInvoke, permanece en la memoria. Después de llamar a EndInvoke, se puede liberar la memoria. Para este caso particular, es posible que la memoria permanezca hasta que el proceso se cierre porque el código de invocación mantiene internamente los datos. Supongo que el GC eventualmente podría recopilarlo, pero no sé cómo el GC sabría que has abandonado los datos en lugar de solo tomarte un tiempo realmente largo para recuperarlos. Dudo que lo haga. Por lo tanto, puede ocurrir una pérdida de memoria.

Se puede encontrar más en http://haacked.com/archive/2009/01/09/asynchronous-fire-and-forget-with-lambdas.aspx

Manoj Aggarwal
fuente
3
El GC puede decir cuándo se abandonan las cosas si no existe ninguna referencia a ellas. Si BeginInvokedevuelve un objeto contenedor que contiene una referencia, pero no es referenciado por, el objeto que contiene los datos del resultado real, el objeto contenedor será elegible para su finalización una vez que se abandonen todas las referencias a él.
supercat
Me pregunto si esto es "no es un buen enfoque"; pruébelo, con las condiciones de error que le preocupan, y vea si tiene problemas. Incluso si la recolección de basura no es perfecta, probablemente no afectará notablemente a la mayoría de las aplicaciones. Según Microsoft, "puede llamar a EndInvoke para recuperar el valor de retorno del delegado, si es necesario, pero esto no es necesario. EndInvoke se bloqueará hasta que se pueda recuperar el valor de retorno". de: msdn.microsoft.com/en-us/library/0b1bf3y3(v=vs.90).aspx
Abacus
5

Casi 10 años después:

Task.Run(FireAway);

Agregaría manejo y registro de excepciones dentro de FireAway

Oscar Fraxedas
fuente
3

El enfoque más simple de .NET 2.0 y posterior es utilizar el modelo de programación asinchnonous (es decir, BeginInvoke en un delegado):

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 );  
}
Ceniza
fuente
Antes de Task.Run()existir, esta era probablemente la forma más concisa de hacer esto.
binki