C# 如何管理许多线程,主要是等待来自数据库的响应



我有 24 个线程,除了进行数据库调用之外,它们几乎不做任何工作。

我根据要查询的数据库将数据集分成几组(总共有 24 个数据库)。然后,我为每个分组创建一个线程。

List<Thread> threads = new List<Thread>();
foreach (var collection in groupedCollections)
{
Thread thread = new Thread(() => myService.MakeDBCalls(collection.ToList());
thread.Start();
threads.Add(thread);
}

在每个线程中,我进行数据库调用,执行非常基本的处理,然后对整个组重复。

如果我的应用程序是单线程的,则数据库调用的延迟需要.015s(我用DateTime.Now测量)。一旦我增加到 24 个线程,我的数据库调用最多需要.3秒。

我怀疑线程相互阻塞。但是,我的 CPU 大部分时间都处于空闲状态,因为线程等待数据库响应的时间99%

有没有一种明智的方法可以让线程休眠,直到它被数据库的响应触发?

有没有一种明智的方法可以让线程休眠,直到它被数据库的响应触发?

最明智的方法是根本不使用线程,而是使用AsyncAPI 进行Db调用,这是一个IO调用,可以awaited,而不涉及任何线程,因此甚至可以根据需要引入concurrency。这是IO / Remote service calls最有效的并发选项,线程不应被浪费。

如果异步 API 不可用?

这对于数据库客户端(您使用哪个客户端?)来说很少见,但是您可以使用TPL将它们包装在Task中,这将从Threadpool获取线程,并且仍然比调用单独的线程更有效。但是,这仍然意味着浪费池中的线程。

记得

Async-Await是理想的,为此,它需要Async启用完整的调用链,以确保在调用继续asynchronously时放弃调用方线程。你也可以考虑ConfigureAwait(false),因为延续可能不需要Synchronization context

您可能在内核少于 24 个的计算机上运行,因此一小部分额外的延迟来自线程争用,但并不多,因为如您所见,您的 CPU 大部分处于空闲状态。

然而:

  • 如果您的数据库与处理线程位于同一台计算机上,则所有这些软件都在查询数据,这些数据最终驻留在通过一根 SATA 电缆连接到主板的单个磁盘上。

  • 如果您的数据库位于不同的计算机上,则考虑您的处理线程在通过单根以太网电缆连接到世界其他地区的机器上运行,您不仅通过该电缆传递数据库请求,还传递传入结果。 然后,你的数据库服务器位于某个地方,我敢打赌你没有24个不同的物理机器,所以其中一些共享相同的硬件,最重要的是相同的磁盘。

我想说的是,即使 CPU 似乎不是一个满足的资源,你的线程总是会争用其他资源。

归根结底,24 个通道的并行化,每个通道的减速率仅为 100% 是相当不错的;你应该认为自己很幸运。

如果您的客户端允许,请以async方式连接到数据库,这样您的线程就不会像现在这样饿死。尽管如此,创建 24 个线程不是一个好的做法,直到您有一台具有类似内核的服务器可用。

如果async数据库操作不可用,则可以为请求引入BlockingCollection,因此您只会同时获得固定数量的请求。

// no more simultaneous tasks than processors available
var dataItems = new BlockingCollection<IList<Data>>(Environment.ProcessorCount);
// if your processing is long enough, consider notifying the scheduler about it
// Task.Factory.StartNew(() => { }, CancellationToken,
//   TaskCreationOptions.LongRunning, TaskScheduler.Default);
Task.Run(() =>
{
while (!dataItems.IsCompleted)
{
IList<Data> data = null;
try
{
data = dataItems.Take();
}
catch (InvalidOperationException) { /* completion called */ }
if (data != null)
{
// service call for a taken data
myService.MakeDBCalls(data);
}
}
});
foreach (var collection in groupedCollections)
{
// Blocks if dataItems.Count == dataItems.BoundedCapacity
dataItems.Add(collection.ToList());
}
// Let consumer know we are done.
dataItems.CompleteAdding();

最新更新