C# 套接字异步与多线程

本文关键字:多线程 异步 套接字 | 更新日期: 2023-09-27 18:35:49

我正在做一个项目,我要从多个服务器(少于1000个)中不断提取信息,并将大部分信息写入数据库。我已将选择范围缩小到 2 个:

编辑:这是一个客户端,所以我将定期生成连接并请求信息。

1 - 使用异步方法,创建 N 个套接字进行轮询,决定是否在回调时将信息写入数据库,并将有用的信息放入缓冲区。然后使用计时器从缓冲区写入信息。

2 - 使用多线程方法,创建 N 个线程,每个线程一个套接字。有用信息的缓冲区将保留在主线程上,循环写入也将保留在主线程上。

这两个选项实际上都使用多个线程,只有第二个似乎增加了手动创建每个线程的额外难度。它有什么优点吗?使用计时器进行书写是否明智?

C# 套接字异步与多线程

对于 1000 个连接,异步 IO 通常是一个好主意,因为它不会在 IO 进行时阻塞线程。(它甚至不使用后台线程来等待。这使得(1)成为更好的选择。

从这个问题中不清楚你需要一个计时器做什么。也许是为了缓冲写入?这将是有效的,但它似乎与这个问题无关。

轮询在现代异步 IO 应用程序中没有位置。完成后,系统会调用您的回调(或完成您的 IO Task)。回调排队到线程池。这使您不必担心。它只是发生。

读取数据的代码应如下所示:

while (true) {
 var msg = await ReadMessageAsync(socket);
 if (msg == null) break;
 await WriteDataAsync(msg);
}

很简单。不会阻塞线程。无回调。

在回答"是否明智地使用计时器"的问题时,也许最好在缓冲区达到特定时间或特定大小时自动刷新。这是内存中缓存在 .NET 框架中的工作方式。缓存同时设置为最大大小和最大过期时间。

故障复原能力可能是一个问题,如果缓冲区是内存中的缓冲区,峰值负载可能会破坏缓冲区。您可以考虑使缓冲区在本地但持久化 - 例如使用 MSMQ 或类似的高速队列技术。我已经看到这成功完成了,特别是如果您使缓冲区写入异步(即"触发并忘记"),它对服务输入队列的能力几乎没有影响,并且允许数据库填充代码在需要时或提示时从持久缓冲区中提取。

另一种选择是拥有一个专用线程,其唯一工作是处理缓冲区并尽可能快地将数据写入数据库。因此,当您建立连接并获取数据时,该数据将放置在缓冲区中。但是您有一个线程始终在查看缓冲区并在数据从其他连接传入时将数据写入数据库。

将缓冲区创建为 BlockingCollection。按照上一个答案中的建议使用异步请求。并且有一个专用线程来读取数据并将其写入数据库:

BlockingCollection<DataType> _theQueue = new BlockingCollection<DataType>(MaxBufferSize);
// add data with
_theQueue.Add(Dataitem);
// service the queue with a simple loop
foreach (var dataItem in _theQueue.GetConsumingEnumerable())
{
    // write dataItem to the database
}

当您想要关闭(即不再从服务器读取数据)时,您将队列标记为已完成以进行添加。然后,使用者线程将清空队列,请注意,它被标记为完成以进行添加,并且循环将退出。

// mark the queue as complete for adding
_theQueue.CompleteAdding();

您需要使缓冲区足够大以处理突发信息。

如果一次向数据库写入一条记录的速度

不够快,则可以修改使用者循环,以用一定数量的记录(10?100?1000?)填充其自己的内部缓冲区,并一次性将它们全部写入数据库。当然,如何做到这一点取决于您的服务器。但是您应该能够提出某种形式的批量插入,以减少您往返数据库的次数。

对于选项 (1),您可以将限定信息写入队列,然后使用数据库编写器侦听队列。这将为您的数据库在峰值负载期间留出一些喘息空间,并避免请求备份等待计时器。

持久队列也会给你一些弹性。