最简单的方法在C#4.0中做一个火灾和遗忘的方法
我真的很喜欢这个问题:
在C#中最简单的方法来做一个消防和遗忘的方法?
我只是想知道,现在我们有在C#4.0中的并行扩展是否有一个更好的更干净的方式来执行Fire&Forget with parallel linq?
Task
类是的,但是PLINQ确实是用来查询集合的。
像下面的东西将与任务做。
Task.Factory.StartNew(() => FireAway());
甚至…
Task.Factory.StartNew(FireAway);
要么…
new Task(FireAway).Start();
FireAway
在哪里FireAway
public static void FireAway() { // Blah... }
所以凭借类和方法的名称简洁,这取决于你select的一个在六到十九个字符之间击败线程池版本:)
ThreadPool.QueueUserWorkItem(o => FireAway());
不是4.0的答案,但值得注意的是,在.Net 4.5中,你可以使这更简单:
#pragma warning disable 4014 Task.Run(() => { MyFireAndForgetMethod(); }).ConfigureAwait(false); #pragma warning restore 4014
该编译指示是禁用警告,告诉你你正在运行这个任务为火和忘记。
如果大括号内的方法返回一个Task:
#pragma warning disable 4014 Task.Run(async () => { await MyFireAndForgetMethod(); }).ConfigureAwait(false); #pragma warning restore 4014
让我们来分解一下:
Task.Run返回一个Task,它生成一个编译器警告(警告CS4014),注意到这个代码将在后台运行 – 这正是你想要的,所以我们禁用警告4014。
默认情况下,任务试图“重新回到原来的线程”,这意味着这个任务将在后台运行,然后尝试返回到启动它的线程。 经常火和忘记任务完成后,原来的线程完成。 这将导致抛出一个ThreadAbortExceptionexception。 在大多数情况下,这是无害的 – 它只是告诉你,我试图重新join,我失败了,但你不在乎。 但是在生产日志或本地开发中的debugging器中,ThreadAbortExceptions仍然有点嘈杂。 .ConfigureAwait(false)
只是一种保持整洁的方式,明确地说,在后台运行这个,就是这样。
由于这是罗嗦,特别是丑陋的编译指示,我使用库方法:
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); } }
用法:
TaskHelper.RunBg(async () => { await doSomethingAsync(); }
我对这个问题的主要答案有几个问题。
首先,在一个真正的开火和忘记的情况下,你可能不会await
任务,所以追加ConfigureAwait(false)
是没有用的。 如果您不await
ConfigureAwait
返回的值,那么它不可能有任何效果。
其次,你需要知道当任务完成时会发生什么情况,例外情况。 考虑一下@ ade-millerbuild议的简单解决scheme:
Task.Factory.StartNew(SomeMethod); // .NET 4.0 Task.Run(SomeMethod); // .NET 4.5
这引入了一个危险:如果一个未处理的exception从SomeMethod()
逃脱,那么这个exception将永远不会被观察到,并且可能会在终结器线程上重新抛出,从而导致应用程序崩溃。 因此,我会build议使用一个辅助方法来确保任何产生的exception都被观察到。
你可以写这样的东西:
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); } } }
这个实现应该具有最小的开销:只有在任务没有成功完成时才会调用continuation,并且应该同步调用continuation(而不是单独调度原始任务)。 在“懒惰”的情况下,你甚至不会为延续代表招致分配。
开始一个asynchronous操作然后变得微不足道:
Blindly.Run(SomeMethod); // Ignore error Blindly.Run(SomeMethod, e => Log.Warn("Whoops", e)); // Log error
1.这是.NET 4.0中的默认行为。 在.NET 4.5中,默认的行为被改变了,以致在终结器线程上不会重新观察未观察到的exception(尽pipe你仍然可以通过TaskScheduler上的UnobservedTaskException事件观察它们)。 但是,可以重写默认configuration,即使您的应用程序需要.NET 4.5,也不应该假定未观察到的任务exception是无害的。
只是为了解决迈克·斯特罗贝尔的答案会发生的一些问题:
如果你使用了var task = Task.Run(action)
并且在给这个任务分配一个继续之后,那么在你给这个Task
分配一个exception处理程序继续之前,你将会面临Task
抛出一些exception的风险。 所以,下面的class级应该没有这个风险:
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; } } }
在这里, task
不是直接运行,而是创build它,然后分配一个延续,只有在这个任务运行的时候,才能消除任务完成执行的风险(或者抛出一些exception),然后再分配一个延续。
Run
方法在这里返回继续Task
所以我可以编写unit testing,确保执行完成。 尽pipe如此,你可以放心地忽略它。