C # 4.0中最简单的生火和遗忘方法

我真的很喜欢这个问题:

Simplest way to do a fire and forget method in C#?

我只是想知道现在我们在 C # 4.0中已经有了并行扩展,是否有一种更简洁的方法来使用并行 linq 来实现 Fire & 勿忘?

89872 次浏览

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

这不是4.0版本的答案,但值得注意的是,在.Net 4.5中,你可以通过以下方法使这个问题更加简单:

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

实用程序将禁用警告,该警告告诉您正在将此 Task 作为 fire 和 forget 运行。

如果花括号内的方法返回 Task:

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

我们来分析一下:

任务。Run 返回一个 Task,它会生成一个编译器警告(警告 CS4014) ,指出这段代码将在后台运行——这正是您想要的,所以我们禁用了警告4014。

默认情况下,Tasks 尝试“元帅回到原来的线程”,这意味着这个 Task 将在后台运行,然后尝试返回到启动它的线程。经常发射和忘记任务完成后,原来的线程完成。这将引发 ThreadAbortException。在大多数情况下,这是无害的-它只是告诉你,我试图重新加入,我失败了,但你不在乎无论如何。但是在生产环境的日志中或者在本地开发的调试器中使用 ThreadAbortException 仍然有点吵。.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();
}

I have a couple issues with the leading answer to this question.

First, in a true 火与遗忘 situation, you probably won't await the task, so it is useless to append ConfigureAwait(false). If you do not await the value returned by ConfigureAwait, then it cannot possibly have any effect.

Second, you need to be aware of what happens when the task completes with an exception. Consider the simple solution that @ade-miller suggested:

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

这会带来一种风险: 如果一个未处理的异常从 SomeMethod()中逃逸,那么这个异常将永远不会被观察到,并且可能 1会在终结器线程上被重新抛出,从而导致应用程序崩溃。因此,我建议使用助手方法来确保观察到任何结果异常。

You could write something like this:

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

这个实现应该具有最小的开销: 只有在任务没有成功完成时才调用延续,并且应该同步调用它(而不是与原始任务分开调度)。在“惰性”情况下,甚至不会为延续委托引起分配。

启动一个异步操作就变得微不足道了:

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

1.中的默认行为。NET 4.0.进去。NET 4.5,默认行为被修改,以便未观察到的异常将在终结器线程上重新抛出 没有(尽管您仍然可以通过 TaskScheduler 上的 Unoble servedTaskException 事件观察到它们)。但是,可以重写默认配置,即使应用程序需要。NET 4.5,您不应该假设未观察到的任务异常将是无害的。

只是为了解决迈克 · 斯特罗贝尔的回答会带来的问题:

如果您使用 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,因此我能够编写单元测试来确保执行完成。但是在使用时可以安全地忽略它。