这是一个场景。由IIS托管的网关。请求可能需要向另一个服务发出多个独立的其他请求,每个服务可能需要几秒钟。当然我认为它是并行化的一个很好的候选者。但是,如果我应该这样做,我会有内部斗争,如果我这样做,那么我应该使用什么样的并行度。我担心的是应用程序中的所有线程通常都是由.net线程池管理的,所以如果我最终分配太多线程(甚至等待),我最终可能会扼杀其他网关功能和API吗?
答案 0 :(得分:8)
PLINQ对于计算绑定操作非常有用,而不是你的情况。对你来说有意义的是Async I / O.
在.NET中有多种方法可以执行异步I / O:普通的APM,TPL,Reactive Extensions,C# 5 async/await,F# async workflows。后三种技术允许您以各种方式组合多个I / O操作,并减少样板代码的数量。
异步I / O不是以任何方式分配新线程。它使用I/O completion ports来避免在等待回复时阻塞线程。
我应该强调这一点:当你在已经并发的环境(IIS)中创建多个长时间运行的请求时,使用异步I / O你绝对是 将获得更好的吞吐量和线程池利用率。
考虑following代码:
Task.Factory.StartNew( () => Parallel.ForEach<Item>(items, item => DoSomething(item)));
当它在客户端程序中使用时,它允许您不要阻止UI线程,同时不必在DoSomething
内异步执行I / O.
当您已经并发服务并且DoSomething
同步执行I / O时,您最终会在繁忙的池中使用其他阻止的线程。如果您为每个传入请求同步执行3个并行传出请求,则10个同时传入的请求将阻止池中的30个线程。这不是一种可扩展性很强的方法。
当你有I / O绑定任务时,你的任务是否安排在新线程上并不重要,重要的是这个任务是否阻止线程。
所以,使用TPL,我看到以下模式比上面的代码更好:
var tasks = new Task<System.Net.WebResponse>[2];
var urls = new string[] { "https://stackoverflow.com/", "http://google.com/" };
for (int i = 0; i < tasks.Length; i++)
{
var wr = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(urls[i]);
tasks[i] = Task.Factory.FromAsync<System.Net.WebResponse>(
wr.BeginGetResponse,
wr.EndGetResponse,
null);
}
var result = Task.Factory.ContinueWhenAll(
tasks,
results => {
foreach (var result in results)
{
var resp = result.Result.GetResponseStream();
// Process responses and combine them into single result
}
});
同样,上面90%的代码是样板。关键特性是WebRequest的BeginGetResponse
异步操作,无论您最终使用哪种更高级别的技术(TPL,Rx等等),都应该使用它。
答案 1 :(得分:3)
您的担忧在某种程度上是有效的。实际上,线程池可能不堪重负。这就是为什么调用长时间运行的非计算IO功能的网关服务通常优先是异步的。异步并不意味着“火与忘”。你仍然可以“等待”,虽然这种等待不再阻止一个线程。
这需要进行大量的代码更改。
一个更简单的解决方案是将负载生成器放入您的服务并测量它是否是一个问题。如果是,快速解决方法是大幅增加线程池大小(我对500-1000个线程(默认值= 250)有积极的体验)。这对吞吐量来说不是最佳的,但它可以工作,可靠并且开发时间很短。只要确保在负载下进行测试,这样你就不会在晚上接到任何令人讨厌的寻呼机呼叫。
请注意,尽管异步现在风靡一时,但在开发人员的工作效率方面却存在缺点。有时产生100个螺纹的良好旧解决方案就足够了,实际上是最好的工程权衡。如果您使用的是C#5.0,则可以利用async/await
更轻松地实现非阻塞IO。