我們正在使用.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核心構建的解決方案
- 6. .Net核心機webfarm的關鍵替代方案
- 7. AppendAllLines替代解決方案
- 8. 替代解決方案underscore.js
- 9. 是核心數據解決方案嗎?
- 10. 需要Thread.sleep()的替代解決方案;
- 11. 解決方案的C++替代算法
- 12. Hibernate.Envers的替代解決方案
- 13. 替代解決方案太多的JOIN
- 14. Row_Number的替代解決方案?
- 15. iframe的替代解決方案?
- 16. C++:模板的替代解決方案
- 17. WINAPI的替代解決方案CreateFile()
- 18. 核心Python中圖像操作的解決方案
- 19. TcpClient的.NET核心替代品
- 20. 尋找.Net解決方案的免費授權解決方案
- 21. 迭代解決方案中的遞歸解決方案
- 22. 正在尋找此陣列解決方案的替代方案
- 23. 有參照舊版本庫問題.NET核心解決方案的設計
- 24. 分頁和替代解決方案
- 25. 替代(?)Java解決方案| ArrayList
- 26. SoapClient未找到替代解決方案
- 27. paymentWithProductIdentifier是什麼替代解決方案?
- 28. 如何在多項目解決方案中使用.net核心依賴注入?
- 29. NET服務解決方案
- 30. 準備好的iCloud +核心數據解決方案
爲什麼選擇投票?對我來說這是一個非常好的問題。 QueueBackgroundWorkItem肯定非常有用。 –
'HostingEnvironment.QueueBackgroundWorkItem'也不安全。它比'Task.Run'更不安全,但它不安全。 –
一個很好的問題。我自己試圖實現一個signalR級別的記者(使用IProgress接口),但由於SignalR的異步性質,我需要將進度報告作爲任務來處理(儘管是非常短暫的任務),而不會減慢他們報告的操作。 – Shazi