我有.NET Core Web API解决方案。在每个调用中,我需要执行一些数据库操作。
问题是同时打开和关闭多个数据库连接。因此,为了避免这种情况,我想实现要发送到数据库的对象队列,然后希望有一个单独的线程来执行db操作。
我尝试了一些下面的代码。但是在这里,消费者线程从不执行分配的功能。 Producer没有单独的线程,我只是在用对象填充队列。
我应该做什么修改?在我刚接触线程时,需要一些指导。
public static class BlockingQueue
{
public static Queue<WebServiceLogModel> queue;
static BlockingQueue()
{
queue = new Queue<WebServiceLogModel>();
}
public static object Dequeue()
{
lock (queue)
{
while (queue.Count == 0)
{
Monitor.Wait(queue);
}
return queue.Dequeue();
}
}
public static void Enqueue(WebServiceLogModel webServiceLog)
{
lock (queue)
{
queue.Enqueue(webServiceLog);
Monitor.Pulse(queue);
}
}
public static void ConsumerThread(IConfiguration configuration)
{
WebServiceLogModel webServiceLog = (WebServiceLogModel)Dequeue();
webServiceLog.SaveWebServiceLog(configuration);
}
public static void ProducerThread(WebServiceLogModel webServiceLog)
{
Enqueue(webServiceLog);
Thread.Sleep(100);
}
}
我已经在StartUp.cs中创建并启动了线程:
public Startup(IConfiguration configuration)
{
Thread t = new Thread(() => BlockingQueue.ConsumerThread(configuration));
t.Start();
}
在Controller中,我编写了代码来填充队列:
[HttpGet]
[Route("abc")]
public IActionResult GetData()
{
BlockingQueue.ProducerThread(logModel);
return StatusCode(HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound, ApplicationConstants.Message.NoBatchHistoryInfo);
}
答案 0 :(得分:1)
首先,尝试避免使用static
类和方法。在这种情况下(如果您确实需要),请使用模式单例。
其次,尝试避免使用lock
,Monitor
-这些并发原语会大大降低性能。
在这种情况下,您可以将BlockingCollection <>用作上述的“ Adam G”,也可以开发自己的解决方案。
public class Service : IDisposable
{
private readonly BlockingCollection<WebServiceLogModel> _packets =
new BlockingCollection<WebServiceLogModel>();
private Task _task;
private volatile bool _active;
private static readonly TimeSpan WaitTimeout = TimeSpan.FromSeconds(1);
public Service()
{
_active = true;
_task = ExecTaskInternal();
}
public void Enqueue(WebServiceLogModel model)
{
_packets.Add(model);
}
public void Dispose()
{
_active = false;
}
private async Task ExecTaskInternal()
{
while (_active)
{
if (_packets.TryTake(out WebServiceLogModel model))
{
// TODO: whatever you need
}
else
{
await Task.Delay(WaitTimeout);
}
}
}
}
public class MyController : Controller
{
[HttpGet]
[Route("abc")]
public IActionResult GetData([FromServices] Service service)
{
// receive model form somewhere
WebServiceLogModel model = FetchModel();
// enqueue model
service.Enqueue(model);
// TODO: return what you need
}
}
在启动中:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<Service>();
// TODO: other init staffs
}
}
您甚至可以将Start / Stop方法添加到服务中,而不用实现IDisposable
,然后在启动类中使用方法Configure(IApplicationBuilder app)
启动服务。
答案 1 :(得分:0)
我认为,如果队列中有内容,那么您的使用者线程仅执行一次,然后立即返回。如果您想让一个线程在后台启动一次仅在后台启动的工作,则该线程永远不要返回并且应该捕获所有异常。来自BlockingQueue.ConsumerThread
的线程在Stratup
中被调用一次并返回。
另外请注意,执行此解决方案并不安全。如果没有任何请求进入,ASP.NET不能保证后台线程正在运行。您的应用程序池可以回收(默认情况下,它在20分钟不活动或每27个小时后回收一次),因此后台有机会代码不会对某些队列项目执行。
此外,尽管它不能解决所有问题,但我建议使用https://www.hangfire.io/在ASP.NET服务器中执行后台任务。它具有持久层,可以重试作业,并具有简单的API。在您的请求处理程序中,您可以将新作业推送到Hangfire,然后只有1个作业处理器线程。