PLINQ或TPL可以窒息.net线程池和IIS请求处理队列吗?

Sch*_*999 5 .net c# multithreading plinq

这是一个场景.由IIS托管的网关.请求可能需要向另一个服务发出多个独立的其他请求,每个服务可能需要几秒钟.当然我认为它是并行化的一个很好的候选者.但是,如果我应该这样做,我会有内部斗争,如果我这样做,那么我应该使用什么样的并行度.我担心的是app中的所有线程通常都是由.net线程池管理的,所以如果我最终分配太多线程(甚至等待),我最终可能会扼杀其他网关功能和API吗?

Art*_*lev 8

PLINQ对于计算绑定操作非常有用,而不是你的情况.对你来说有意义的是Async I/O.

在.NET中有多种方法可以执行异步I/O:普通旧APM,TPL,Reactive Extensions,C#5异步/等待,F#异步工作流.后三种技术允许您以各种方式组合多个I/O操作,并减少样板代码的数量.

异步I/O不是以任何方式分配新线程.它使用I/O完成端口来避免在等待回复时阻塞线程.

我应该强调这一点:当你在已经并发的环境(IIS)中创建多个长时间运行的请求时,使用异步I/O 肯定会获得更好的吞吐量和线程池利用率.

请考虑以下代码:

Task.Factory.StartNew( () => Parallel.ForEach<Item>(items, item => DoSomething(item)));
Run Code Online (Sandbox Code Playgroud)

当它在客户端程序中使用时,它允许您不要阻止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
        }
    });
Run Code Online (Sandbox Code Playgroud)

同样,上面90%的代码都是样板.关键特性是WebRequest的BeginGetResponse异步操作,无论您最终使用哪种更高级别的技术(TPL,Rx等等),都应该使用它.