我真的很喜欢这个问题:
Simplest way to do a fire and forget method in C#?
我只是想知道,现在我们在C#4.0中有并行扩展,有更好的清洁方法来做Fire&忘记并行linq?
答案 0 :(得分:83)
不是4.0的答案,但值得注意的是,在.Net 4.5中,您可以通过以下方式使这更简单:
#pragma warning disable 4014
Task.Run(() =>
{
MyFireAndForgetMethod();
}).ConfigureAwait(false);
#pragma warning restore 4014
该pragma是禁用警告,告诉您将此任务视为火灾而忘记。
如果花括号内的方法返回任务:
#pragma warning disable 4014
Task.Run(async () =>
{
await MyFireAndForgetMethod();
}).ConfigureAwait(false);
#pragma warning restore 4014
让我们打破这一点:
Task.Run返回一个Task,它会生成编译器警告(警告CS4014),注意到此代码将在后台运行 - 这正是您想要的,所以我们禁用警告4014.
默认情况下,Tasks尝试“Marshal回到原始线程”,这意味着此任务将在后台运行,然后尝试返回启动它的线程。经常解雇并忘记在完成原始Thread之后完成任务。这将导致抛出ThreadAbortException。在大多数情况下,这是无害的 - 它只是告诉你,我试图重新加入,我失败了,但你无论如何都不在乎。但是,在生产中的日志或本地开发人员的调试器中使用ThreadAbortExceptions仍然有点吵。 .ConfigureAwait(false)
只是保持整洁的一种方式,明确地说,在后台运行,就是这样。
由于这是冗长的,特别是丑陋的pragma,我使用了一个库方法:
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();
}
答案 1 :(得分:82)
Task
类是,但是PLINQ实际上是用于查询集合。
以下内容将使用Task执行此操作。
Task.Factory.StartNew(() => FireAway());
甚至......
Task.Factory.StartNew(FireAway);
或者...
new Task(FireAway).Start();
FireAway
public static void FireAway()
{
// Blah...
}
因此,凭借类和方法名称的简洁性,这会使线程池版本超过6到19个字符,具体取决于您选择的字符:)
ThreadPool.QueueUserWorkItem(o => FireAway());
答案 2 :(得分:23)
我对这个问题的主要答案有几个问题。
首先,在一个真正的 fire-and-forget 情况下,你可能不会await
这个任务,所以追加ConfigureAwait(false)
是没用的。如果您没有await
ConfigureAwait
返回的值,那么它就不会产生任何影响。
其次,您需要了解任务以异常完成时会发生什么。考虑@ ade-miller建议的简单解决方案:
Task.Factory.StartNew(SomeMethod); // .NET 4.0
Task.Run(SomeMethod); // .NET 4.5
这引入了一个危险:如果未处理的异常从SomeMethod()
中逃脱,则永远不会观察到该异常,并且可能会在终结器线程上重新抛出 1 ,从而导致应用程序崩溃。因此,我建议使用辅助方法来确保观察到任何结果异常。
你可以这样写:
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,并且应该同步调用它(而不是与原始任务分开调度)。在&#34;懒惰&#34;例如,你甚至不会为继续代表招致分配。
开始异步操作然后变得微不足道:
Blindly.Run(SomeMethod); // Ignore error
Blindly.Run(SomeMethod, e => Log.Warn("Whoops", e)); // Log error
<子> 1。这是.NET 4.0中的默认行为。在.NET 4.5中,更改了默认行为,以便在终结器线程上重新抛出 not 未观察到的异常(尽管您仍可以通过TaskScheduler上的UnobservedTaskException事件观察它们)。但是,可以覆盖默认配置,即使您的应用程序需要.NET 4.5,也不应该假设未观察到的任务异常将是无害的。
答案 3 :(得分:5)
只是为了解决Mike Strobel的答案会发生的一些问题:
如果您使用var task = Task.Run(action)
并在为该任务分配延续后,那么在为Task
分配异常处理程序延续之前,您将遇到Task
抛出异常的风险。因此,下面的课程应该没有这种风险:
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
不是直接运行,而是创建它,分配一个延续,然后运行任务以消除任务完成执行(或抛出一些异常)的风险分配延续。
此处Run
方法返回继续Task
,因此我可以编写单元测试以确保执行完成。您可以放心地在使用中忽略它。