我们正在使用.NET Core Web Api,并寻找一种轻量级解决方案来将具有可变强度的请求记录到数据库中,但不希望客户端等待保存过程。
不幸的是没有HostingEnvironment.QueueBackgroundWorkItem(..)
在dnx
和Task.Run(..)
实施是不安全的。
有没有优雅的解决方案?.NET中的HostingEnvironment.QueueBackgroundWorkItem的替代解决方案核心
回答
您可以在.NET Core中使用Hangfire(http://hangfire.io/)作为后台作业。
例如:
var jobId = BackgroundJob.Enqueue(
() => Console.WriteLine("Fire-and-forget!"));
QueueBackgroundWorkItem
走了,但我们已经有了IApplicationLifetime
代替IRegisteredObject
,正在使用由前一个。我想这对于这样的场景看起来很有希望。这个想法(我还不太确定,如果是非常糟糕的;因此,小心!)是注册一个单身人士,其产生和观察新的任务。在那个单例中,我们还可以注册一个“停止的事件”,以便正确地等待仍在运行的任务。
这个“概念”可以用于短时间运行的东西,如日志记录,邮件发送等。事情不应该花费太多时间,但会对当前请求产生不必要的延迟。
public class BackgroundPool
{
protected ILogger<BackgroundPool> Logger { get; }
public BackgroundPool(ILogger<BackgroundPool> logger, IApplicationLifetime lifetime)
{
if (logger == null)
throw new ArgumentNullException(nameof(logger));
if (lifetime == null)
throw new ArgumentNullException(nameof(lifetime));
lifetime.ApplicationStopped.Register(() =>
{
lock (currentTasksLock)
{
Task.WaitAll(currentTasks.ToArray());
}
logger.LogInformation(BackgroundEvents.Close, "Background pool closed.");
});
Logger = logger;
}
private readonly object currentTasksLock = new object();
private readonly List<Task> currentTasks = new List<Task>();
public void SendStuff(Stuff whatever)
{
var task = Task.Run(async() =>
{
Logger.LogInformation(BackgroundEvents.Send, "Sending stuff...");
try
{
// do THE stuff
Logger.LogInformation(BackgroundEvents.SendDone, "Send stuff returns.");
}
catch (Exception ex)
{
Logger.LogError(BackgroundEvents.SendFail, ex, "Send stuff failed.");
}
});
lock (currentTasksLock)
{
currentTasks.Add(task);
currentTasks.RemoveAll(t => t.IsCompleted);
}
}
}
这种BackgroundPool
应注册为独立的,并可以通过经由DI的任何其它部件一起使用。我目前正在使用它发送邮件,并且它工作正常(在应用程序关闭期间也测试了邮件发送)。
注意:访问像后台任务中的当前HttpContext
这样的东西应该不起作用。无论如何,old solution使用UnsafeQueueUserWorkItem
来禁止。
您认为如何?
更新:
在ASP.NET 2.0的核心有后台任务,从而获得更好的ASP.NET 2.1核心的新东西:Implementing background tasks in .NET Core 2.x webapps or microservices with IHostedService and the BackgroundService class
这里是一个Axel's answer版本扭捏,让你传入代表并对已完成的任务进行更积极的清理。
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;
namespace Example
{
public class BackgroundPool
{
private readonly ILogger<BackgroundPool> _logger;
private readonly IApplicationLifetime _lifetime;
private readonly object _currentTasksLock = new object();
private readonly List<Task> _currentTasks = new List<Task>();
public BackgroundPool(ILogger<BackgroundPool> logger, IApplicationLifetime lifetime)
{
if (logger == null)
throw new ArgumentNullException(nameof(logger));
if (lifetime == null)
throw new ArgumentNullException(nameof(lifetime));
_logger = logger;
_lifetime = lifetime;
_lifetime.ApplicationStopped.Register(() =>
{
lock (_currentTasksLock)
{
Task.WaitAll(_currentTasks.ToArray());
}
_logger.LogInformation("Background pool closed.");
});
}
public void QueueBackgroundWork(Action action)
{
#pragma warning disable 1998
async Task Wrapper() => action();
#pragma warning restore 1998
QueueBackgroundWork(Wrapper);
}
public void QueueBackgroundWork(Func<Task> func)
{
var task = Task.Run(async() =>
{
_logger.LogTrace("Queuing background work.");
try
{
await func();
_logger.LogTrace("Background work returns.");
}
catch (Exception ex)
{
_logger.LogError(ex.HResult, ex, "Background work failed.");
}
}, _lifetime.ApplicationStopped);
lock (_currentTasksLock)
{
_currentTasks.Add(task);
}
task.ContinueWith(CleanupOnComplete, _lifetime.ApplicationStopping);
}
private void CleanupOnComplete(Task oldTask)
{
lock (_currentTasksLock)
{
_currentTasks.Remove(oldTask);
}
}
}
}
就像在Axel的回答中一样,您实际上并不等待从“Task.WaitAll(currentTask.ToArray());”返回的任务。 – Shazi
如@axelheer提到IHostedService是在.NET核心2.0及以上的路要走。
我需要一个类似于ASP.NET Core的替代HostingEnvironment.QueueBackgroundWorkItem的轻量级,所以我写了使用.NET Core的2.0 IHostedService的DalSoft.Hosting.BackgroundQueue。
PM>安装,包装DalSoft.Hosting.BackgroundQueue
在你的ASP.NET核心启动。CS:
public void ConfigureServices(IServiceCollection services)
{
services.AddBackgroundQueue(onException:exception =>
{
});
}
要排队一个后台任务只需添加BackgroundQueue
到控制器的构造函数,并调用Enqueue
。
public EmailController(BackgroundQueue backgroundQueue)
{
_backgroundQueue = backgroundQueue;
}
[HttpPost, Route("/")]
public IActionResult SendEmail([FromBody]emailRequest)
{
_backgroundQueue.Enqueue(async cancellationToken =>
{
await _smtp.SendMailAsync(emailRequest.From, emailRequest.To, request.Body);
});
return Ok();
}
- 1. .net核心的FtpWebRequest替代方案
- 2. Ruby解决方案替代.NET解决方案
- 3. 替代的解决方案
- 4. WebHostBuilder.Build().NET中的MissingMethodException核心迁移解决方案
- 5. .Net核心机webfarm的关键替代方案
- 6. AppendAllLines替代解决方案
- 7. 需要Thread.sleep()的替代解决方案;
- 8. C++:模板的替代解决方案
- 9. WINAPI的替代解决方案CreateFile()
- 10. Hibernate.Envers的替代解决方案
- 11. 核心Python中图像操作的解决方案
- 12. TcpClient的.NET核心替代品
- 13. 寻找.Net解决方案的免费授权解决方案
- 14. 分页和替代解决方案
- 15. 替代(?)Java解决方案| ArrayList
- 16. SoapClient未找到替代解决方案
- 17. 如何在多项目解决方案中使用.net核心依赖注入?
- 18. 在Asp.Net中转换app.config文件核心解决方案发布?
- 19. 在同一个解决方案中组合ASP.NET和ASP.NET核心
- 20. com +在.net中的替代方案?
- 21. .NET Core中的SignedCms替代方案
- 22. 在Vista中代替XP开发.Net解决方案的优点和缺点
- 23. .NET WinForms ToolStrip替代方案
- 24. Java解析当前解决方案的替代
- 25. XBTT或替代解决方案中的全局计数器?
- 26. 在T-SQL中显示表格的替代解决方案
- 27. MySQL中的错误1449 - 替代解决方案?
- 28. 我想要AOP的.net解决方案?
- 29. .net 4.0上的ERP解决方案
- 30. .net与Maven的解决方案结构?
为什么选择投票?对我来说这是一个非常好的问题。 QueueBackgroundWorkItem肯定非常有用。 –
'HostingEnvironment.QueueBackgroundWorkItem'也不安全。它比'Task.Run'更不安全,但它不安全。 –
一个很好的问题。我自己试图实现一个signalR级别的记者(使用IProgress接口),但由于SignalR的异步性质,我需要将进度报告作为任务来处理(尽管是非常短暂的任务),而不会减慢他们报告的操作。 – Shazi