.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
是完全无用的,因为计时器将 运行 线程池上的滴答事件,而不管启动它的线程是什么。至少除非指定了同步对象。
我正在尝试在 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
是完全无用的,因为计时器将 运行 线程池上的滴答事件,而不管启动它的线程是什么。至少除非指定了同步对象。