简体   繁体   English

如何减少基于数据库的多线程通知/电子邮件发件人中的CPU使用率

[英]How to reduce CPU usage in a db-based multi threaded notification/email sender

I am trying to develop a windows service to send notifications to subscriptions. 我正在尝试开发Windows服务以将通知发送到订阅。 The data is saved in a SQL server database. 数据保存在SQL Server数据库中。

Notifications are created by making a web POST request to a REST API endpoint and saved in a database table. 通知是通过向REST API端点发出Web POST请求来创建的,并保存在数据库表中。

The service starts one Task that keeps reading notifications from this db table and add them to a queue. 该服务启动一个任务,该任务不断从该数据库表中读取通知并将其添加到队列中。

Also the service starts few Tasks that keep reading from the queue and do the actual send process. 此外,该服务还会启动一些任务,这些任务会不断从队列中读取数据并执行实际的发送过程。

The code is working good and doing the job needed, but the problem is that CPU usage is 100% when running the service. 该代码运行良好并且可以完成所需的工作,但是问题是运行服务时CPU使用率为100%。

I tried to use Thread.Sleep or Task.Delay but neither helped me to reduce the CPU usage. 我尝试使用Thread.Sleep或Task.Delay,但都没有帮助我减少CPU使用率。

I have read in this codeprojct page , that I need to use wait handlers and should wait on some condition. 我已经在此codeprojct 页面中阅读了我需要使用等待处理程序,并且应该在某些条件下等待。 I could not have this working properly. 我无法正常工作。

so can anyone advise what can I do to reduce CPU usage for EnqueueTask and DequeueTask ? 所以谁能建议我该怎么做以减少EnqueueTaskDequeueTask CPU使用率?

Here is the sender code: 这是发件人代码:

static class NotificationSender
{
    static ConcurrentQueue<NotificationDelivery> deliveryQueue = null;
    static Task enqueueTask = null;
    static Task[] dequeueTasks = null;

    public static void StartSending(ServiceState serviceState)
    {
        PushService.InitServices();

        enqueueTask = Task.Factory.StartNew(EnqueueTask, serviceState);

        deliveryQueue = new ConcurrentQueue<NotificationDelivery>();

        int dequeueTasksCount = 10;
        dequeueTasks = new Task[dequeueTasksCount];
        for (int i = 0; i < dequeueTasksCount; i++)
        {
            dequeueTasks[i] = Task.Factory.StartNew(DequeueTask, serviceState);
        }
    }

    public static void EnqueueTask(object state)
    {
        ServiceState serviceState = (ServiceState)state;

        using (DSTeckWebPushNotificationsContext db = new DSTeckWebPushNotificationsContext())
        {
            while (!serviceState.CancellationTokenSource.Token.IsCancellationRequested)
            {
                int toEnqueue = 100 - deliveryQueue.Count;

                if (toEnqueue > 0)
                {
                    // fetch some records from db to be enqueued
                    NotificationDelivery[] deliveries = db.NotificationDeliveries
                        .Include("Subscription")
                        .Include("Notification")
                        .Include("Notification.NotificationLanguages")
                        .Include("Notification.NotificationLanguages.Language")
                        .Where(nd => nd.Status == NotificationDeliveryStatus.Pending && DateTime.Now >= nd.StartSendingAt)
                        .OrderBy(nd => nd.StartSendingAt)
                        .Take(toEnqueue)
                        .ToArray();

                    foreach (NotificationDelivery delivery in deliveries)
                    {
                        delivery.Status = NotificationDeliveryStatus.Queued;
                        deliveryQueue.Enqueue(delivery);
                    }

                    if (deliveries.Length > 0)
                    {
                        db.SaveChanges(); // save Queued state, so not fetched again the next loop
                    }
                }

                // save any changes made by the DequeueTask
                // an event may be used here to know if any changes made
                db.SaveChanges();
            }

            Task.WaitAll(dequeueTasks);
            db.SaveChanges();
        }
    }

    public async static void DequeueTask(object state)
    {
        ServiceState serviceState = (ServiceState)state;

        while (!serviceState.CancellationTokenSource.Token.IsCancellationRequested)
        {
            NotificationDelivery delivery = null;

            if (deliveryQueue.TryDequeue(out delivery))
            {
                NotificationDeliveryStatus ns = NotificationDeliveryStatus.Pending;
                if (delivery.Subscription.Status == SubscriptionStatus.Subscribed)
                {
                    PushResult result = await PushService.DoPushAsync(delivery);

                    switch (result)
                    {
                        case PushResult.Pushed:
                            ns = NotificationDeliveryStatus.Delivered;
                            break;
                        case PushResult.Error:
                            ns = NotificationDeliveryStatus.FailureError;
                            break;
                        case PushResult.NotSupported:
                            ns = NotificationDeliveryStatus.FailureNotSupported;
                            break;
                        case PushResult.UnSubscribed:
                            ns = NotificationDeliveryStatus.FailureUnSubscribed;
                            delivery.Subscription.Status = SubscriptionStatus.UnSubscribed;
                            break;
                    }
                }
                else
                {
                    ns = NotificationDeliveryStatus.FailureUnSubscribed;
                }

                delivery.Status = ns;
                delivery.DeliveredAt = DateTime.Now;
            }
        }
    }

    public static void Wait()
    {
        Task.WaitAll(enqueueTask);
        Task.WaitAll(dequeueTasks);

        enqueueTask.Dispose();
        for(int i = 0; i < dequeueTasks.Length; i++)
        {
            dequeueTasks[i].Dispose();
        }
    }
}

An object of type ServiceState is used to maintain starting and stopping the service, and here is the code for this type: 类型为ServiceState的对象用于维护服务的启动和停止,这是此类型的代码:

class ServiceState
{
    public CancellationTokenSource CancellationTokenSource { get; set; }

    public void Start()
    {
        CancellationTokenSource = new CancellationTokenSource();

        NotificationSender.StartSending(this);
    }

    public void Stop()
    {
        CancellationTokenSource.Cancel();

        NotificationSender.Wait();
        CancellationTokenSource.Dispose();
    }
}

Here is the service start and stop code: 这是服务的启动和停止代码:

protected override void OnStart(string[] args)
{
    _serviceState = new ServiceState();
    _serviceState.Start();
}

protected override void OnStop()
{
    _serviceState.Stop();
}

I think I could finally do good changes to maintain the CPU usage using wait handlers and a timer. 我认为我终于可以使用等待处理程序和计时器来进行良好的更改以维持CPU使用率。

EnqueueTask will wait 5 seconds before trying to fetch data again from the notifications table if no notifications fetched. 如果没有获取通知, EnqueueTask将等待5秒,然后尝试再次从通知表获取数据。 If no notifications fetched, it will start the timer and reset the wait handle. 如果未获取任何通知,它将启动计时器并重置等待句柄。 The timer elapsed callback will then set the wait handle. 经过计时器的回调将设置等待句柄。

Also DequeueTask is now using a wait handle. 另外, DequeueTask现在正在使用等待句柄。 If no more items in the queue, it will reset the wait handle to stop dequeue-ing empty queue. 如果队列中没有更多项目,它将重置等待句柄以停止使空队列出队。 EnqueueTask will set this wait handle when it adds items to the queue. EnqueueTask在将项目添加到队列时将设置此等待句柄。

CPU usage is now <= 10% 现在的CPU使用率<= 10%

And here is the updated NotificationSender code: 这是更新的NotificationSender代码:

static class NotificationSender
{
    static ConcurrentQueue<NotificationDelivery> deliveryQueue = null;
    static Task enqueueTask = null;
    static Task[] dequeueTasks = null;

    static ManualResetEvent enqueueSignal = null;
    static ManualResetEvent dequeueSignal = null;

    static System.Timers.Timer enqueueTimer = null;

    public static void StartSending(CancellationToken token)
    {
        PushService.InitServices();

        using (DSTeckWebPushNotificationsContext db = new DSTeckWebPushNotificationsContext())
        {
            NotificationDelivery[] queuedDeliveries = db.NotificationDeliveries
                        .Where(nd => nd.Status == NotificationDeliveryStatus.Queued)
                        .ToArray();

            foreach (NotificationDelivery delivery in queuedDeliveries)
            {
                delivery.Status = NotificationDeliveryStatus.Pending;
            }

            db.SaveChanges();
        }

        enqueueSignal = new ManualResetEvent(true);
        dequeueSignal = new ManualResetEvent(false);

        enqueueTimer = new System.Timers.Timer();
        enqueueTimer.Elapsed += EnqueueTimerCallback;
        enqueueTimer.Interval = 5000;
        enqueueTimer.AutoReset = false;
        enqueueTimer.Stop();

        enqueueTask = new Task(EnqueueTask, token, TaskCreationOptions.LongRunning);
        enqueueTask.Start();

        deliveryQueue = new ConcurrentQueue<NotificationDelivery>();

        int dequeueTasksCount = 10;
        dequeueTasks = new Task[dequeueTasksCount];
        for (int i = 0; i < dequeueTasksCount; i++)
        {
            dequeueTasks[i] = new Task(DequeueTask, token, TaskCreationOptions.LongRunning);
            dequeueTasks[i].Start();
        }
    }

    public static void EnqueueTimerCallback(Object source, ElapsedEventArgs e)
    {
        enqueueSignal.Set();
        enqueueTimer.Stop();
    }

    public static void EnqueueTask(object state)
    {
        CancellationToken token = (CancellationToken)state;

        using (DSTeckWebPushNotificationsContext db = new DSTeckWebPushNotificationsContext())
        {
            while (!token.IsCancellationRequested)
            {
                if (enqueueSignal.WaitOne())
                {
                    int toEnqueue = 100 - deliveryQueue.Count;

                    if (toEnqueue > 0)
                    {
                        // fetch some records from db to be enqueued
                        NotificationDelivery[] deliveries = db.NotificationDeliveries
                            .Include("Subscription")
                            .Include("Notification")
                            .Include("Notification.NotificationLanguages")
                            .Include("Notification.NotificationLanguages.Language")
                            .Where(nd => nd.Status == NotificationDeliveryStatus.Pending && DateTime.Now >= nd.StartSendingAt)
                            .OrderBy(nd => nd.StartSendingAt)
                            .Take(toEnqueue)
                            .ToArray();

                        foreach (NotificationDelivery delivery in deliveries)
                        {
                            delivery.Status = NotificationDeliveryStatus.Queued;
                            deliveryQueue.Enqueue(delivery);
                        }

                        if (deliveries.Length > 0)
                        {
                            // save Queued state, so not fetched again the next loop
                            db.SaveChanges();

                            // signal the DequeueTask
                            dequeueSignal.Set();
                        }
                        else
                        {
                            // no more notifications, wait 5 seconds before try fetching again
                            enqueueSignal.Reset();
                            enqueueTimer.Start();
                        }
                    }

                    // save any changes made by the DequeueTask
                    // an event may be used here to know if any changes made
                    db.SaveChanges();
                }
            }

            Task.WaitAll(dequeueTasks);
            db.SaveChanges();
        }
    }

    public async static void DequeueTask(object state)
    {
        CancellationToken token = (CancellationToken)state;

        while (!token.IsCancellationRequested)
        {
            if (dequeueSignal.WaitOne()) // block untill we have items in the queue
            {
                NotificationDelivery delivery = null;

                if (deliveryQueue.TryDequeue(out delivery))
                {
                    NotificationDeliveryStatus ns = NotificationDeliveryStatus.Pending;
                    if (delivery.Subscription.Status == SubscriptionStatus.Subscribed)
                    {
                        PushResult result = await PushService.DoPushAsync(delivery);

                        switch (result)
                        {
                            case PushResult.Pushed:
                                ns = NotificationDeliveryStatus.Delivered;
                                break;
                            case PushResult.Error:
                                ns = NotificationDeliveryStatus.FailureError;
                                break;
                            case PushResult.NotSupported:
                                ns = NotificationDeliveryStatus.FailureNotSupported;
                                break;
                            case PushResult.UnSubscribed:
                                ns = NotificationDeliveryStatus.FailureUnSubscribed;
                                delivery.Subscription.Status = SubscriptionStatus.UnSubscribed;
                                break;
                        }
                    }
                    else
                    {
                        ns = NotificationDeliveryStatus.FailureUnSubscribed;
                    }

                    delivery.Status = ns;
                    delivery.DeliveredAt = DateTime.Now;
                }
                else
                {
                    // empty queue, no more items
                    // stop dequeueing untill new items added by EnqueueTask
                    dequeueSignal.Reset();
                }
            }
        }
    }

    public static void Wait()
    {
        Task.WaitAll(enqueueTask);
        Task.WaitAll(dequeueTasks);

        enqueueTask.Dispose();
        for(int i = 0; i < dequeueTasks.Length; i++)
        {
            dequeueTasks[i].Dispose();
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM