¿La forma más simple de hacer fuego y olvidar el método en C#?
.net nonblocking (8)
Vi en WCF que tienen el [OperationContract(IsOneWay = true)]
. Pero WCF parece un poco lento y pesado para crear una función que no sea de bloqueo. Idealmente, habría algo así como static void nonblocking MethodFoo(){}
, pero no creo que exista.
¿Cuál es la forma más rápida de crear una llamada a 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 superior n. ° 2) Si el método debe finalizar, en algunos lugares, como una aplicación ASP.NET, deberá hacer algo para bloquear y mantener el hilo activo. De lo contrario, esto podría llevar a "fire-forget-but-never-actually-execute", 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 )
El enfoque más simple de .NET 2.0 y posterior es el uso del 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 );
}
La forma recomendada de hacerlo cuando usa Asp.Net y .Net 4.5.2 es mediante QueueBackgroundWorkItem
. Aquí hay una clase de ayuda:
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 asincrónico:
BackgroundTaskRunner.FireAndForgetTask(async () =>
{
await FireAway();
});
Esto funciona muy bien en los sitios web de Azure.
Llamar a beginInvoke y no atrapar 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 arroja 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, la memoria se puede liberar. Para este caso particular, es posible que la memoria permanezca hasta que el proceso se apague debido a que 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 simplemente tomar un tiempo realmente largo para recuperarlo. 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
Para .NET 4.5:
Task.Run(() => FireAway());
Para C # 4.0 y más reciente, me parece que la mejor respuesta ahora la ofrece Ade Miller: la forma más sencilla de hacer un método de disparo y olvídate en c # 4.0
Task.Factory.StartNew(() => FireAway());
O incluso...
Task.Factory.StartNew(FireAway);
O...
new Task(FireAway).Start();
Donde
FireAway
es
public static void FireAway() { // Blah... }
Por lo tanto, en virtud de la brevedad del nombre de clase y método, esto supera la versión de subprocesos entre seis y diecinueve caracteres, dependiendo de la que elijas :)
ThreadPool.QueueUserWorkItem(o => FireAway());
Para agregar a la respuesta de Will , si se trata de una aplicación de consola, simplemente AutoResetEvent
un AutoResetEvent
y un WaitHandle
para evitar que salga antes de que finalice el hilo del trabajador:
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();
}
}
Una manera fácil es crear y comenzar 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 asignarle un nombre a su nuevo hilo para facilitar la depuración. Además, no se olvide de utilizar un manejo de errores extenso en su rutina porque cualquier excepción no controlada fuera de un depurador bloqueará abruptamente su aplicación:
ThreadPool.QueueUserWorkItem(o => FireAway());
(cinco años después...)
Task.Run(() => FireAway());
como lo señala luisperezphd .