消费者生产者-生产者线程从不执行分配的功能

时间:2018-11-21 06:54:15

标签: c# multithreading .net-core queue thread-synchronization

我有.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);
    }

2 个答案:

答案 0 :(得分:1)

首先,尝试避免使用static类和方法。在这种情况下(如果您确实需要),请使用模式单例。 其次,尝试避免使用lockMonitor-这些并发原语会大大降低性能。 在这种情况下,您可以将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个作业处理器线程。