.Net Core 中的多线程工作者服务

Multi Thread Worker Service in .Net Core

我正在尝试在 Core 5.0 上构建辅助服务。我的树基本上就是这样=>
1 -) Program.cs 2-) Worker.cs 3-) MyStartUp.cs 4-) Client.cs

在 MyStartUp.cs 中,我正在获取一个列表并根据列表调用客户端 class 一些服务器。
在客户端class,我连接到设备并将读取的数据写入数据库。
设备数量近1200台,服务器方式为TCP/IP.
对于编写这样的工作人员服务,您最好的建议是什么? 如何以最佳形式使用线程?

以下是我的第一次尝试。这种形式是有效的,但是对于 1000 个不同的客户端来说它太慢了,因为客户端中有太多的读取。

public class Worker : BackgroundService
{
    private readonly ILogger<Worker> _logger;

    public Worker(ILogger<Worker> logger)
    {
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        StartUp startUp = new StartUp();
    }
}

public class StartUp
{
    public StartUp()
    {
        //... get client datas and initialize client object
        StartClients();
    }
    public void StartClients()
    {
        foreach (var item in ClientList)
        {
            new Thread(item.Run).Start();
        }
    }
}

public class Client
{
    System.Timers.Timer timer ;
    public Client()
    {
        timer = new Timer();
        timer.Interval = 100;
        timer.Elapsed += Timer_Elapsed;
        
        //... initialize client connection and database
    }
    public void Run()
    {
        timer.Start();
    }
    private void Timer_Elapsed(object sender, ElapsedEventArgs e)
    {
        //... write values of read client to database
    }
}

假设您有 1k 个定时器,每 100 毫秒 运行,并且每个定时器滴答需要 50 毫秒来执行。这意味着每个计时器需要 500 毫秒/秒,或一个内核的 50%,并且您需要 500 个内核才能跟上工作。您可能没有那么多内核,也没有 IO 来处理请求,这意味着工作将开始堆积并且您的计算机或多或少会死机,因为它没有时间更新 UI.

50 毫秒可能高估了所用时间,但即使是 5 毫秒,您也可能会遇到问题,除非您 运行在怪物服务器上执行此操作。

解决方案是将轮询频率降低到更合理的水平,比如每 100 秒而不是每 100 毫秒。或者让一个或多个线程尽可能快地轮询您的设备。例如:

private BlockingCollection<MyClient> clients = new ();
private List<Task> workers = new ();
public void StartWorker(){
    workers.Add(Task.Run(Run));
    void Run(){
        foreach(var client in clients.GetConsumingEnumerable()){
           // Do processing
           clients.Add(client); // re-add client to queue
      }
   } 
}
public void CloseAllWorkers(){
    clients.CompleteAdding();
    Task.WhenAll(workers).Wait();
}

我会注意到 Thread 的用法大部分已被任务取代。并且创建一个线程只是为了启动一个 System.Timers.Timer 是完全无用的,因为计时器将 运行 线程池上的滴答事件,而不管启动它的线程是什么。至少除非指定了同步对象。