La forma más simple de hacer un fuego y olvidar el método en c # 4.0

Me gusta mucho esta pregunta:

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

Solo quiero saber que ahora que tenemos extensiones paralelas en C # 4.0 ¿hay alguna manera mejor y más limpia de Fire & Forget con Parallel linq?

Con la clase de Task sí, pero PLINQ es realmente para consultar sobre las colecciones.

Algo como lo siguiente lo hará con Tarea.

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

No es una respuesta para 4.0, pero vale la pena señalar que en .Net 4.5 puede hacer esto aún más simple con:

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

El pragma es desactivar la advertencia que le dice que está ejecutando esta tarea como fuego y olvidarse.

Si el método dentro de las llaves vuelve una Tarea:

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

Vamos a desglosar eso:

Task.Run devuelve una Tarea, que genera una advertencia del comstackdor (advertencia CS4014) y señala que este código se ejecutará en segundo plano: eso es exactamente lo que quería, por lo que desactivamos la advertencia 4014.

De forma predeterminada, las tareas intentan “Mariscal de nuevo en el subproceso original”, lo que significa que esta tarea se ejecutará en segundo plano, y luego intentará volver al subproceso que la inició. A menudo, las tareas de disparo y olvido terminan una vez que se completa el hilo original. Eso provocará que se genere una ThreadAbortException. En la mayoría de los casos, esto es inofensivo; solo se lo digo, intenté volver a unirme, fallé, pero de todos modos no me importa. Pero todavía es un poco ruidoso tener ThreadAbortExceptions en tus registros en Production o en tu depurador en el desarrollador local. .ConfigureAwait(false) es solo una forma de mantenerse ordenado y explícitamente decir, ejecutar esto en segundo plano, y eso es todo.

Como esto es prolijo, especialmente el pragma feo, utilizo un método de biblioteca para esto:

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

Uso:

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

Tengo un par de problemas con la respuesta principal a esta pregunta.

Primero, en una verdadera situación de ” olvidar y olvidar “, probablemente no await la tarea, por lo tanto, es inútil agregar ConfigureAwait(false) . Si no await el valor devuelto por ConfigureAwait , entonces no puede tener ningún efecto.

En segundo lugar, debe tener en cuenta lo que sucede cuando la tarea se completa con una excepción. Considere la solución simple que @ ade-miller sugirió:

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

Esto introduce un peligro: si una excepción no controlada se escapa de SomeMethod() , esa excepción nunca se observará, y puede 1 volverse a lanzar en el hilo del finalizador, bloqueando su aplicación. Por lo tanto, recomendaría utilizar un método de ayuda para garantizar que se observen las excepciones resultantes.

Podrías escribir algo como esto:

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

Esta implementación debe tener una sobrecarga mínima: la continuación solo se invoca si la tarea no se completa correctamente y debe invocarse de forma síncrona (en lugar de progtwigrse por separado de la tarea original). En el caso “flojo”, ni siquiera incurrirá en una asignación para el delegado de continuación.

Comenzar una operación asincrónica se vuelve trivial:

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

1. Este fue el comportamiento predeterminado en .NET 4.0. En .NET 4.5, el comportamiento predeterminado se modificó para que las excepciones no observadas no se volvieran a generar en el hilo del finalizador (aunque aún puede observarlas a través del evento UnobservedTaskException en TaskScheduler). Sin embargo, la configuración predeterminada puede ser anulada, e incluso si su aplicación requiere .NET 4.5, no debe asumir que las excepciones de tareas no observadas serán inofensivas.

Solo para solucionar un problema que sucederá con la respuesta de Mike Strobel:

Si usa var task = Task.Run(action) y luego asigna una continuación a esa tarea, correrá el riesgo de que Task ejecute alguna excepción antes de asignar una continuación del manejador de excepciones a la Task . Por lo tanto, la siguiente clase debe estar libre de este riesgo:

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

Aquí, la task no se ejecuta directamente, sino que se crea, se le asigna una continuación y solo luego se ejecuta la tarea para eliminar el riesgo de que la tarea complete la ejecución (o arroje alguna excepción) antes de asignar una continuación.

El método Run aquí devuelve la Task continuación para que pueda escribir pruebas de unidad y asegúrese de que la ejecución esté completa. Sin embargo, puedes ignorarlo sin problemas en tu uso.