运行“一劳永逸”工作的一贯方式是什么
ASP.NET 不是为即发即弃的工作而设计的。它旨在服务于 HTTP 请求。当生成 HTTP 响应时(当您的操作返回时),该请求/响应周期就完成了。
请注意,只要没有活动的请求,ASP.NET 就可以随时关闭您的 AppDomain。这通常在不活动超时后或当您的 AppDomain 进行了一定数量的垃圾收集时在共享主机上完成,或者完全无缘无故地每 29 小时进行一次。
所以你并不是真的想要“一发不可收拾”——你想要产生响应但又不想让ASP.NET 忘记它。的简单解决方案ConfigureAwait(false)
会让每个人都忘记它,这意味着一旦在一个蓝月亮,你的延续可能会“迷失”。
我有一篇博文详细介绍了这个主题。简而言之,您希望在生成响应之前将要在持久层(如 Azure 表)中完成的工作记录下来。这是理想的解决方案。
如果您不打算做理想的解决方案,那么您将过着危险的生活。在我的博客文章中有代码将Task
s 注册到 ASP.NET 运行时,以便您可以提前返回响应,但通知 ASP.NET 您还没有真正完成。这将防止 ASP.NET 在您有出色的工作时关闭您的站点,但它不会保护您免受更根本的故障,例如硬盘驱动器崩溃或有人绊倒您的服务器电源线。
我的博客文章中的代码在下面重复;这取决于AsyncCountdownEvent
我的AsyncEx 库中的:
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Hosting;
using Nito.AsyncEx;
/// <summary>
/// A type that tracks background operations and notifies ASP.NET that they are still in progress.
/// </summary>
public sealed class BackgroundTaskManager : IRegisteredObject
{
/// <summary>
/// A cancellation token that is set when ASP.NET is shutting down the app domain.
/// </summary>
private readonly CancellationTokenSource shutdown;
/// <summary>
/// A countdown event that is incremented each time a task is registered and decremented each time it completes. When it reaches zero, we are ready to shut down the app domain.
/// </summary>
private readonly AsyncCountdownEvent count;
/// <summary>
/// A task that completes after <see cref="count"/> reaches zero and the object has been unregistered.
/// </summary>
private readonly Task done;
private BackgroundTaskManager()
{
// Start the count at 1 and decrement it when ASP.NET notifies us we're shutting down.
shutdown = new CancellationTokenSource();
count = new AsyncCountdownEvent(1);
shutdown.Token.Register(() => count.Signal(), useSynchronizationContext: false);
// Register the object and unregister it when the count reaches zero.
HostingEnvironment.RegisterObject(this);
done = count.WaitAsync().ContinueWith(_ => HostingEnvironment.UnregisterObject(this), TaskContinuationOptions.ExecuteSynchronously);
}
void IRegisteredObject.Stop(bool immediate)
{
shutdown.Cancel();
if (immediate)
done.Wait();
}
/// <summary>
/// Registers a task with the ASP.NET runtime.
/// </summary>
/// <param name="task">The task to register.</param>
private void Register(Task task)
{
count.AddCount();
task.ContinueWith(_ => count.Signal(), TaskContinuationOptions.ExecuteSynchronously);
}
/// <summary>
/// The background task manager for this app domain.
/// </summary>
private static readonly BackgroundTaskManager instance = new BackgroundTaskManager();
/// <summary>
/// Gets a cancellation token that is set when ASP.NET is shutting down the app domain.
/// </summary>
public static CancellationToken Shutdown { get { return instance.shutdown.Token; } }
/// <summary>
/// Executes an <c>async</c> background operation, registering it with ASP.NET.
/// </summary>
/// <param name="operation">The background operation.</param>
public static void Run(Func<Task> operation)
{
instance.Register(Task.Run(operation));
}
/// <summary>
/// Executes a background operation, registering it with ASP.NET.
/// </summary>
/// <param name="operation">The background operation.</param>
public static void Run(Action operation)
{
instance.Register(Task.Run(operation));
}
}
它可以像这样用于async
或同步代码:
BackgroundTaskManager.Run(() =>
{
// Synchronous example
Thread.Sleep(20000);
});
BackgroundTaskManager.Run(async () =>
{
// Asynchronous example
await Task.Delay(20000);
});