Windows服务的并行编程

本文关键字:编程 并行 服务 Windows | 更新日期: 2023-09-27 18:15:37

我有一个Windows Service,它的代码类似如下:

List<Buyer>() buyers = GetBuyers();
var results = new List<Result();
Parallel.Foreach(buyers, buyer =>
{
    // do some prep work, log some data, etc.
    // call out to an external service that can take up to 15 seconds each to return
    results.Add(Bid(buyer));    
}
// Parallel foreach must have completed by the time this code executes
foreach (var result in results)
{
    // do some work
}

这一切都很好,它工作,但我认为我们正在遭受可伸缩性问题。我们平均每分钟有20-30个入站连接,每个连接都会触发此代码。每个入站连接的"买家"集合可以包含1-15个买家。有时候,我们的入站连接数会达到每分钟100多个连接,我们的服务器会陷入停顿。

每个服务器(两个负载均衡的8核服务器)上的CPU使用率仅为50%左右,但线程数继续增加(进程上高达350个线程),每个入站连接的响应时间从3-4秒增加到1.5-2分钟。

我怀疑上面的代码是导致我们的可伸缩性问题的原因。在Windows服务(没有UI)上给出这种使用场景(I/O操作的并行性),是并行的。每一种最好的方法是什么?我没有太多的异步编程经验,我期待着利用这个机会学习更多关于它的知识,我想我应该从这里开始获得一些社区建议,以补充我在谷歌上能够找到的东西。

Windows服务的并行编程

Parallel.Foreach有一个可怕的设计缺陷。随着时间的推移,它很容易消耗所有可用的线程池资源。它将生成的线程数量实际上是无限的。你可以每秒得到2个新答案,这是由没人理解的启发式驱动的。CoreCLR有一个内建的爬坡算法,就是不能工作。

调用外部服务

也许,您应该找出调用该服务的正确并行度。你需要通过测试不同的量来找出答案。

然后,您需要限制Parallel.Foreach只生成您想要的最大数量的线程。你可以使用一个固定的并发TaskScheduler

或者,您将其更改为使用异步IO并使用SemaphoreSlim.WaitAsync。这样就不会有线程被阻塞。此方法解决了池耗尽和外部服务的过载问题。