rkr*_*ter 9 asp.net asp.net-mvc asp.net-2.0 asp.net-mvc-2
很抱歉这个关于异步操作的愚蠢问题.这就是我理解它的方式.
IIS有一组有限的工作线程在等待请求.如果一个请求是长时间运行的操作,它将阻止该线程.这导致更少的线程来处理请求.
修复此问题的方法 - 使用异步页面.当请求进入时,主工作线程被释放,而另一个线程在其他地方创建.因此,主线程能够满足其他请求.当请求在另一个线程上完成时,从主线程池中挑选另一个线程,并将响应发送回客户端.
1)这些其他线程在哪里?还有另一个线程池吗?
2)如果ASP.NET喜欢在这个其他线程池(?)中创建新线程,为什么不增加主工作池中的线程数 - 它们都在同一台机器上运行?我没有看到将该请求移动到其他线程池的优势.内存/ CPU应该是一样的吗?
3)如果主线程将请求移交给另一个线程,为什么请求不会断开?它神奇地将请求移交给其他地方的另一个工作线程,当长时间运行的进程完成时,它从主工作池中选择一个线程并向客户端发送响应.我很惊讶......但是这有什么作用?
小智 10
您没有说明您正在使用的是哪个版本的IIS或ASP.NET.很多人都在谈论IIS和ASP.NET,就像它们是同一个一样,但它们实际上是两个组件协同工作.请注意,IIS 6和7会侦听I/O完成端口,从而从HTTP.sys中获取完成.IIS线程池用于此目的,它的最大线程数为256.此线程池的设计使其无法很好地处理长时间运行的任务.IIS团队的建议是切换到另一个线程,如果你要做大量的工作,比如IIS 7上的ASP.NET ISAPI和/或ASP.NET"集成模式"处理程序完成的工作.否则你会打平IIS线程并防止IIS从HTTP.sys中获取完成情况您可能不关心任何这些,因为您不是在编写本机代码,也就是说,您没有编写ISAPI或本机处理程序IIS 7管道.您可能只是使用ASP.NET,在这种情况下,您对其线程池及其工作方式更感兴趣.
http://blogs.msdn.com/tmarq/archive/2007/07/21/asp-net-thread-usage-on-iis-7-0-and-6-0.aspx上有一篇博文,解释了ASP.NET如何使用线程.请注意,对于IIS 7上的ASP.NET v2.0和v3.5,您应该将MaxConcurrentRequestsPerCPU增加到5000 - 这是在这些平台上默认设置为12的错误.IIS 7上的ASP.NET v4.0中MaxConcurrentRequestsPerCPU的新默认值为5000.
回答你的三个问题:
1)首先,一点引物.每个CPU一次只能执行1个线程.如果你有更多,你需要支付罚金 - 每次CPU切换到另一个线程时都需要上下文切换,这些都很昂贵.但是,如果线程被阻塞等待工作......那么切换到另一个可以立即执行的线程是有意义的.
因此,如果我有一个线程正在进行大量的计算工作并大量使用CPU,这需要很长时间,我应该切换到另一个线程吗?没有!当前线程正在有效地使用CPU,因此切换只会产生上下文切换的成本.
因此,如果我有一个线程向另一台服务器发出HTTP或SOAP请求并需要很长时间,我应该切换线程吗?是! 您可以异步执行HTTP或SOAP请求,这样一旦发生"发送",您可以展开当前线程,并且在"接收"的I/O完成之前不使用任何线程.在"发送"和"接收"之间,远程服务器很忙,因此本地您不需要在线程上阻塞,而是使用.NET Framework中提供的异步API,以便您可以放松和完成后通知.
好的,所以你问的第一个问题是"这些其他线程在哪里?是否有另一个线程池?" 这取决于.在.NET Framework中运行的大多数代码都使用CLR ThreadPool,它包含两种类型的线程,工作线程和I/O完成线程.那些不使用CLR ThreadPool的代码呢?好吧,它可以创建自己的线程,使用自己的线程池,或任何它想要的,因为它可以访问操作系统提供的Win32 API.基于我们之前讨论的内容,线程来自哪里并不重要,并且就操作系统和硬件而言,线程是一个线程.
2)在第二个问题中,您说:"我没有看到将该请求移动到其他线程池的优势." 你认为切换没有任何优势是正确的,除非你要弥补刚刚执行的昂贵的上下文切换以便切换.这就是我向远程服务器提供慢速HTTP或SOAP请求示例的原因,作为切换的一个很好的理由.顺便说一句,ASP.NET不会创建任何线程.它使用CLR ThreadPool,该池中的线程完全由CLR管理.他们在确定何时需要更多线程方面做得非常好.例如,这就是为什么ASP.NET可以轻松地从同时执行1个请求扩展到同时执行300个请求,而无需执行任何操作.传入的请求通过调用QueueUserWorkItem发布到CLR ThreadPool,CLR决定何时调用WaitCallback(参见MSDN).
3)第三个问题是,"如果主线程将请求移交给另一个线程,为什么请求不会被断开?" 好吧,当请求最初到达服务器时,IIS从HTTP.sys中获取I/O完成.IIS然后调用ASP.NET的处理程序(或ISAPI).ASP.NET立即将请求排队到CLR Threadpool,并将待处理状态返回给IIS.这个待处理状态告诉IIS我们还没有完成,但是一旦完成,我们就会通知您.现在,ASP.NET管理该请求的生命周期.当CLR ThreadPool线程调用ASP.NET WaitCallback(请参阅MSDN)时,它可以在该线程上执行整个请求,这是正常情况.或者它可以切换到一个或多个其他线程,如果请求是我们称之为异步的 - 即它有一个异步模块或处理程序.无论哪种方式,都有明确定义的请求完成方式,当它最终完成时,ASP.NET将告诉IIS我们已经完成,IIS将最终字节发送到客户端并关闭连接,如果Keep-Alive是没被使用.
此致,托马斯
在ASP.NET异步页面使用异步回调和异步回调使用线程池,它是在同一用来为ASP.NET请求的线程池.
然而,它并不那么简单..NET ThreadPool有两种类型的线程 - 工作线程和I/O线程.I/O线程使用所谓的I/O完成端口,这是一个(在这里大大过于简单化)无线程或线程不可知的方法,等待文件句柄上的读/写操作完成,随后运行回调方法.
(请注意,文件句柄不一定是指磁盘上的文件;就Windows而言,它也可以是套接字,管道等)
一个典型的.NET Web开发人员并不需要知道任何这些.当然,如果您正在编写实际的Web服务器或任何类型的网络服务器,那么您肯定需要了解这些,因为它们是处理数百个传入连接的唯一方法,而不会实际产生数百个线程来为它们提供服务.有一个管理的I/O完成端口教程(CodeProject上)如果你有兴趣.
无论如何,回到主题; 当您在高级别与线程池交互时,即通过写入:
ThreadPool.QueueUserWorkItem(s => DoSomeWork(s));
Run Code Online (Sandbox Code Playgroud)
这并没有使用I/O完成端口.永远.它将工作发布到由线程池管理的正常工作线程之一.如果使用异步回调,它也是一样的:
Func<int> asyncFunc;
IAsyncResult BeginOperation(object sender, EventArgs e, AsyncCallback cb,
object state)
{
asyncFunc = () => { Thread.Sleep(500); return 42; };
return asyncFunc.BeginInvoke(cb, state);
}
void EndOperation(IAsyncResult ar)
{
int result = asyncFunc.EndInvoke(ar);
Console.WriteLine(result);
}
Run Code Online (Sandbox Code Playgroud)
再次 - 同样的交易.里面的EndOperation你在运行ThreadPool 工作者线程.您可以通过插入以下调试代码来验证这一点:
void EndSimpleWait(IAsyncResult ar)
{
int maxWorkers, maxIO, availableWorkers, availableIO;
ThreadPool.GetMaxThreads(out maxWorkers, out maxIO);
ThreadPool.GetAvailableThreads(out availableWorkers, out availableIO);
int result = asyncFunc.EndInvoke(ar);
}
Run Code Online (Sandbox Code Playgroud)
在那里打一个断点,你会发现它availableWorkers比一个断点少maxWorkers,maxIO而且availableIO是相同的.
但是一些异步操作在.NET中是"特殊的".这实际上与ASP.NET没有直接关系 - 他们也会在Winforms或WPF应用程序中使用I/O完成端口.例如:
System.Net.Sockets.Socket(BeginReceive)和一大堆其他BeginXYZ方法)System.IO.FileStream(BeginRead和BeginWrite)System.ServiceModel.ClientBase<T>(BeginInvoke)System.Net.WebRequest(BeginGetResponse)等等,这远不是一个完整的清单.在公开自己的.NET框架基本上几乎所有的类BeginXYZ和EndXYZ方法,并可以想见,执行任何I/O,可能使用I/O完成端口.这是为了让您(应用程序开发人员)更容易,因为I/O线程很难在.NET中实现.
我的猜测是,.NET Framework设计人员故意选择让发布I/O操作变得困难(与工作线程相比,你可以在那里写ThreadPool.QueueUserWorkItem),因为如果你不知道如何正确使用它们会比较"危险" ; 相比之下,在Windows API中生成这些实际上非常简单.
和以前一样,您可以使用一些调试代码验证发生了什么:
WebRequest request;
IAsyncResult BeginDownload(object sender, EventArgs e,
AsyncCallback cb, object state)
{
request = WebRequest.Create("http://www.example.com");
return request.BeginGetResponse(cb, state);
}
void EndDownload(IAsyncResult ar)
{
int maxWorkers, maxIO, availableWorkers, availableIO;
ThreadPool.GetMaxThreads(out maxWorkers, out maxIO);
ThreadPool.GetAvailableThreads(out availableWorkers, out availableIO);
string html;
using (WebResponse response = request.EndGetResponse(ar))
{
using (StreamReader reader = new
StreamReader(response.GetResponseStream()))
{
html = reader.ReadToEnd();
}
}
}
Run Code Online (Sandbox Code Playgroud)
如果您单步执行此操作,您将看到线程统计信息不同.在availableWorkers将匹配maxWorkers,但availableIO比少一个maxIO.那是因为你在I/O线程上运行.这也是为什么你不应该在异步回调中进行任何昂贵的计算 - 在I/O完成端口上发布CPU密集型工作是低效的,而且很糟糕.
所有这些都解释了为什么强烈建议您在需要执行任何I/O操作时在ASP.NET中使用Async页面.该模式仅对I/O操作有用; 非I/O异步操作最终将被发布到工作线程中ThreadPool,您最终仍会阻止后续的ASP.NET请求.但是你可以产生几乎无限数量的异步I/O操作,而不是再考虑一下; 在I/O完成并且回调准备好开始之前,它们根本不会使用任何线程.
总而言之 - 只有一个ThreadPool,但它中有不同类型的线程,如果你执行慢速I/O操作,那么使用I/O线程会更有效.它与CPU或内存无关,它与I/O和文件句柄有关.
至于#3,它不是真正的"为什么问题没有请求断开连接",更像一个问题:"为什么会呢?" 套接字不会因为当前没有线程发送到它或从中接收数据而关闭,就像没有人在那里迎接客人一样,你的前门也不会自动关闭.如果服务器没有应答它们,客户端操作可能会超时,并且可能随后选择断开它们的端点,但这完全是另一个问题.
| 归档时间: |
|
| 查看次数: |
2576 次 |
| 最近记录: |