简体   繁体   English

.NET5 后台服务并发

[英].NET5 Background Service Concurrency

I have a background service that will be started when the application performing startup.我有一个后台服务,它将在应用程序执行启动时启动。 The background service will start to create multiple tasks based on how many workers are set.后台服务将根据设置的工人数量开始创建多个任务。 As I do various trials and monitor the open connection on DB.当我进行各种试验并监视 DB 上的打开连接时。 The open connection is always the same value as the worker I set.打开的连接总是与我设置的工人相同。 Let say I set 32 workers, then the connection will be always 32 open connections shown as I use query to check it.假设我设置了 32 个工作人员,那么连接将始终显示为 32 个打开的连接,因为我使用查询来检查它。 FYI I am using Postgres as the DB server.仅供参考,我使用 Postgres 作为数据库服务器。 In order to check the open connection, I use the query below to check the connection when the application is running.为了检查打开的连接,我使用下面的查询来检查应用程序运行时的连接。

select * from pg_stat_activity where application_name = 'myapplication';

Below is the background service code.下面是后台服务代码。

public class MessagingService : BackgroundService {
     private int worker = 32;

     protected override async Task ExecuteAsync(CancellationToken cancellationToken) {
         var tasks = new List<Task>();
         for (int i=0; i<worker; i++) {
         tasks.Add(DoJob(cancellationToken));
         }
         while (!cancellationToken.IsCancellationRequested) {
            try {
               var completed = await Task.WhenAny(tasks);
               tasks.Remove(completed);
            } catch (Exception) {
               await Task.Delay(1000, cancellationToken);
            }
            if (!cancellationToken.IsCancellationRequested) {
               tasks.Add(DoJob(cancellationToken));
            }
        }
     }
    private async Task DoJob(CancellationToken cancellationToken) {
        using (var scope = _services.CreateScope()) {
            var service = scope.ServiceProvider
                .GetRequiredService<MessageService>();
            try {
                //do select and update query on db if null return false otherwise send mail
                if (!await service.Run(cancellationToken)) {
                    await Task.Delay(1000, cancellationToken);
                }
            } catch (Exception) {
                await Task.Delay(1000, cancellationToken);
            }
        }
    }
}   

The workflow is not right as it will keep creating the task and leave the connection open and idle.工作流程不正确,因为它将继续创建任务并使连接保持打开和空闲状态。 Also, the CPU and memory usage are high when running those tasks.此外,运行这些任务时,CPU 和 memory 使用率很高。 How can I achieve like when there is no record found on DB only keep 1 worker running at the moment?当在 DB 上找不到任何记录时,我如何才能实现目前仅保持 1 个工作人员运行? If a record or more is found it will keep increasing until the preset max worker then decreasing the worker when the record is less than the max worker.如果找到一条或更多记录,它将继续增加,直到预设的最大工作人员,然后当记录小于最大工作人员时减少工作人员。 If this question is too vague or opinion-based then please let me know and I will try my best to make it as specific as possible.如果这个问题太模糊或基于意见,请告诉我,我会尽力使其尽可能具体。

Update Purpose更新目的

The purpose of this service is to perform email delivery.此服务的目的是执行 email 交付。 There is another API that will be used to create a scheduled job.还有另一个 API 将用于创建计划作业。 Once the job is added to the DB, this service will do the email delivery at the scheduled time.将作业添加到数据库后,此服务将在预定时间进行 email 交付。 Eg, 5k schedule jobs are added to the DB and the scheduled time to perform the job is '2021-12-31 08:00:00' and the time when creating the scheduled job is 2021-12-31 00:00:00'.例如,将 5k 个计划作业添加到数据库中,执行该作业的计划时间为 '2021-12-31 08:00:00',创建计划作业的时间为 2021-12-31 00:00:00 '。 The service will keep on looping from 00:00:00 until 08:00:00 with 32 workers running at the same time then just start to do the email delivery.该服务将从 00:00:00 一直循环到 08:00:00,同时运行 32 个工作人员,然后开始执行 email 交付。 How can I improve it to more efficiency like normally when there is no job scheduled only 1 worker is running.当没有安排只有 1 个工作人员正在运行的作业时,我怎样才能像往常一样提高它的效率。 When it checked there is 5k scheduled job it will fully utilise all the worker.当它检查有 5k 计划作业时,它将充分利用所有工作人员。 After 5k job is completed, it will back to 1 workers.完成 5k 作业后,将返回 1 个工人。

My suggestion is to spare yourself from the burden of manually creating and maintaining worker tasks, by using an ActionBlock<T> from the TPL Dataflow library.我的建议是通过使用TPL 数据流库中的ActionBlock<T>来免除手动创建和维护工作任务的负担。 This component is a combination of an input queue and an Action<T> delegate.该组件是输入队列和Action<T>委托的组合。 You specify the delegate in its constructor, and you feed it with messages with its Post method.您在其构造函数中指定委托,并使用其Post方法向其提供消息。 When there are no more messages, you notify it by invoking its Complete method, and then await its Completion so that you know that all work that was delegated to it has completed.当没有更多消息时,您通过调用它的Complete方法来通知它,然后await它的Completion以便您知道委托给它的所有工作都已完成。

Below is a rough demonstration if how you could use this component:下面是一个粗略的演示,如果你可以使用这个组件:

protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
    var processor = new ActionBlock<Job>(async job =>
    {
        await ProcessJob(job);
        await MarkJobAsCompleted(job);
    }, new ExecutionDataflowBlockOptions()
    {
        MaxDegreeOfParallelism = 32
    });

    try
    {
        while (true)
        {
            Task delayTask = Task.Delay(TimeSpan.FromSeconds(60), cancellationToken);
            Job[] jobs = await FetchReadyToProcessJobs();
            foreach (var job in jobs)
            {
                processor.Post(job);
                await MarkJobAsPending(job);
            }
            await delayTask; // Will throw when the token is canceled
        }
    }
    finally
    {
        processor.Complete();
        await processor.Completion;
    }
}

The FetchReadyToProcessJobs method is supposed to connect to the database, and fetch all the jobs whose time has come to be processed. FetchReadyToProcessJobs方法应该连接到数据库,并获取所有需要处理的作业。 In the above example this method is invoked every 60 seconds.在上面的示例中,此方法每 60 秒调用一次。 The Task.Delay is created before invoking the method, and awaited after the returned jobs have been posted to the ActionBlock<T> . Task.Delay在调用该方法之前创建,并在返回的作业发布到ActionBlock<T>之后等待。 This way the interval between invocations will be stable and consistent.这样调用之间的间隔将是稳定和一致的。

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

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