.NET中辅助线程和I / O线程的简单描述


69

在.NET中很难找到工作线程和I / O线程的详细而简单的描述

对于该主题,我很清楚(但在技术上可能不准确):

  • 辅助线程是使用CPU进行工作的线程。
  • I / O线程(也称为“完成端口线程”)使用设备驱动程序进行工作,并且实质上“不执行任何操作”,仅监视非CPU操作的完成情况。

不清楚的是:

  • 尽管ThreadPool.GetAvailableThreads方法返回两种类型的可用线程数,但似乎没有公共API可以为I / O线程安排工作。您只能在.NET中手动创建辅助线程?
  • 似乎单个I / O线程可以监视多个I / O操作。是真的吗 如果是这样,为什么默认情况下ThreadPool有这么多可用的I / O线程?
  • 在某些文本中,我读到了由I / O线程执行I / O操作完成后触发的回调。是真的吗 考虑到此回调是CPU操作,这不是工作线程的工作吗?
  • 更具体地说-ASP.NET异步页面是否使用I / O线程?将I / O工作切换到单独的线程而不是增加工作线程的最大数量时,性能上的好处到底是什么?是因为单个I / O线程会监视多个操作吗?还是Windows在使用I / O线程时进行更有效的上下文切换?

Answers:


73

.net / CLR中的“工作线程”一词通常仅指主线程以外的任何线程,它代表生成该线程的应用程序执行某些“工作”。“工作”实际上可能意味着任何事情,包括等待一些I / O完成。ThreadPool保留工作线程的高速缓存,因为创建线程非常昂贵。

.net / CLR中的术语“ I / O线程”是指ThreadPool保留的线程,用于从“重叠”的win32调用(也称为“完成端口I / O”)分派NativeOverlapped回调。CLR维护自己的I / O完成端口,并且可以将任何句柄绑定到该端口(通过ThreadPool.BindHandle API)。此处的示例:http : //blogs.msdn.com/junfeng/archive/2008/12/01/threadpool-bindhandle.aspx。许多.net API在内部使用此机制来接收NativeOverlapped回调,尽管典型的.net开发人员永远不会直接使用它。

“工作线程”和“ I / O线程”之间实际上并没有技术上的区别,它们都是普通线程。但是CLR ThreadPool保留了每个单独的池,只是为了避免对工作线程的高需求耗尽所有可用于分发本机I / O回调的线程,从而可能导致死锁。(想象一个使用全部250个工作线程的应用程序,其中每个线程都在等待某个I / O完成)。

开发人员在处理I / O回调时确实需要格外小心,以确保将I / O线程返回到ThreadPool中-也就是说,I / O回调代码应完成服务回调所需的最少工作然后将线程的控制权返回给CLR线程池。如果需要更多工作,则应在工作线程上安排该工作。否则,应用程序可能会“劫持”用作普通工作线程的CLR保留I / O完成线程池,从而导致上述死锁情况。

一些很好的参考,可供进一步阅读:win32 I / O完成端口:http : //msdn.microsoft.com/zh-cn/library/aa365198( VS.85) .aspx托管线程池:http : //msdn.microsoft.com /en-us/library/0ka9477y.aspx BindHandle的示例:http : //blogs.msdn.com/junfeng/archive/2008/12/01/threadpool-bindhandle.aspx


1
因此,一些公开APM模式的API(例如-WebRequest)在内部使用ThreadPool.BindHandle方法。BeginXXX方法将接受用户回调委托,将工作交给某些设备驱动程序,并将ThreadPool I / O线程保留为“等待”完成端口,以通知外部设备工作完成。收到通知后,I / O线程将唤醒并运行用户回调委托代码(应快速将I / O线程快速返回到池中)。
康斯坦丁

1
因此,工作线程通常按用户代码安排运行工作。而且I / O线程通常按框架代码安排运行工作,该框架代码与外部设备进行交互,并且需要在其“外部”工作完成后等待其工作完成并运行用户回调逻辑。这都是正确的理解吗?
康斯坦丁

2
几乎可以,但是OS如何调度实际的I / O对于CLR是不透明的。CLR的I / O线程不会被束缚以等待完成。操作系统会在工作完成时通过I / O完成端口警告CLR。
alexdej'2

1
我知道这个问题很旧,但是也许有些人仍然可以为我解答:如果我调用多个BeginXXX方法(例如,多个客户端网络流BeginRead),它们中的每一个都会得到自己的I / O线程,或者所有它们都在其中处理相同的I / O线程?我问这个问题,因为我想让一个服务器上有很多客户端,如果所有这些BeginX方法都拥有自己的线程,这可能会引起问题,不确定这一点。
R1PFake

12

我将从描述NT中程序如何使用异步I / O开始。

您可能熟悉Win32 API函数ReadFile(作为示例),它是Native API函数NtReadFile的包装。此功能使您可以对异步I / O进行两件事:

  • 您可以创建一个事件对象,并将其传递给NtReadFile。当读取操作完成时,将通知该事件。
  • 您可以将异步过程调用(APC)函数传递给NtReadFile。从本质上讲,这意味着读操作完成时,该函数将排队到发起该操作的线程中,并且在线程执行可警告的wait时将执行该函数。

但是,还有第三种方法可以在I / O操作完成时得到通知。您可以创建一个I / O完成端口对象,并将文件句柄与其关联。每当对与I / O完成端口关联的文件完成操作时,操作的结果(如I / O状态)就会排队到I / O完成端口。然后,您可以设置一个专用线程以从队列中删除结果,并执行适当的任务,例如调用回调函数。这本质上就是“ I / O工作线程”。

普通的“工作线程”非常相似。它不是从队列中删除I / O结果,而是从队列中删除工作项。您可以将工作项(QueueUserWorkItem)排队,并让工作线程执行它们。这样可以避免您每次要异步执行任务时都必须生成线程。


4

简单地说,一个工作线程旨在执行短时间的工作,并在完成工作后将其自身删除。回调可用于通知父进程它已完成或传递回数据。

I / O线程将连续执行相同的操作或一系列操作,直到被父进程停止为止。之所以这样称呼,是因为通常设备驱动程序会连续运行以监视设备端口。一个I / O线程通常会在希望与其他线程通信时创建事件。

所有进程都作为线程运行。您的应用程序作为线程运行。任何线程都可以生成工作线程或I / O线程(如您所称)。

性能与所使用线程的数量或类型之间始终始终保持良好的平衡。进程处理的太多回调或事件将严重降低其性能,这是由于处理它们的主进程循环的中断次数所致。

辅助线程的示例是在用户交互之后将数据添加到数据库中,或者执行长时间的数学计算或将数据写入文件。通过使用辅助线程,您可以释放主应用程序,这对于GUI最为有用,因为它在执行任务时不会冻结。


1

技能比我高的人会来这里帮忙。

工作线程有很多状态,它们由处理器等调度,您可以控制它们的所有工作。

IO完成端口由操作系统提供,用于执行非常少的共享状态的非常特定的任务,因此使用起来更快。.Net中的一个很好的例子是WCF框架。实际上,对WCF服务的每个“调用”都是由IO完成端口执行的,因为它们启动速度最快,并且操作系统会为您照顾它们。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.