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

时间:2017-04-13 17:30:46

标签: c# database multithreading async-await task-parallel-library

我有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%

是否有一种合理的方法让线程在数据库响应触发之前进入休眠状态?

3 个答案:

答案 0 :(得分:2)

  

是否有一种合理的方法让线程在数据库响应触发之前进入休眠状态?

最明智的方法是根本不使用线程,而是使用Async API进行Db调用,这是IO调用,可以是{{1不涉及任何线程,甚至根据需要引入awaited。这是concurrency最有效的并发选项,不应浪费线程。

  

如果Async API不可用?

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

  

记住

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

答案 1 :(得分:1)

你可能在一台机器上运行的内核少于24个,所以额外延迟的一小部分来自线程争用,但并不多,因为正如你所看到的,你的CPU大多是空闲的。

然而:

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

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

我想要的是,即使CPU看起来不是一个满足的资源,但是线程总是会争用其他资源。

在一天结束时,每通道减速仅为100%的24个并行化通道相当不错;你应该认为自己很幸运。

答案 2 :(得分:0)

如果您的客户端允许,请以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();