Welcome

首页 / 软件开发 / .NET编程技术 / .NET:轮询访问ThreadPool

.NET:轮询访问ThreadPool2011-12-14 msdn Stephen Toub问:我目前正在使用 Microsoft .NET Framework ThreadPool,在使用过程中我遇到了一种情况,不知道该如何解决。我需要处理一个较大批次的已在队列中的工作项,在第一个批次开始处理后第二个批次(规模稍小)到达。最初,较大批次中的一些工作将被分配给 ThreadPool 中的所有工作线程。但是,当第二批到达后,我想平均分配,使每一批都得到同样的服务,而不是先到的批次得到所有线程。

当其中一批完成时,我希望无论哪一个仍需处理的批次,都能够获得所有工作线程的关注。我该如何在 ThreadPool 顶层对此类批处理功能进行分层呢?

答:在以前的专栏中,我曾介绍过如何在现有 .NET ThreadPool 的顶层对各种类型的功能进行分层。在 2004 年 10 月刊的《MSDN 杂志》中,我介绍了如何使 ThreadPool 支持等待排队的工作项(请参见“ThreadPoolWait 和 HandleLeakTracker”)。在 2004 年 11 月刊中,我介绍了如何添加对工作项优先级的支持(请参见“ThreadPoolPriority 和 MethodImplAttribute”)。在 2006 年 3 月刊中,我介绍了如何添加取消支持(请参见“可以取消的线程池”)。将来,我可以回顾 2009 年 1 月这一期,并提到我曾介绍过如何在 ThreadPool 中添加轮询调度支持。

您需要解决的问题首先是要了解 ThreadPool 分派工作的原理。在内部,它维护一个已被加入其队列的工作项队列。当池中的某个线程可以执行工作时,它会返回到此工作队列并提取下一个项目。此处理顺序并未记录在案,因此是非常不可靠的(因为它可能而且很可能会在未来版本中有所变更)。

现在它可以通过一种非常简单的方式来实现:先进先出 (FIFO) 队列。因此,要排队的第一个工作将成为被线程所选中的第一个工作。在您的批处理情况中,这意味着第一批中的所有工作在队列中都将排在第二批所有工作的前面。因此,第一批的所有工作都将在第二批的所有工作之前进行分派。对于某些情况这是合理且最佳的方法。而对于您的情况,还需要进行更多的控制。

从 ThreadPool 获得这种控制的最简单方法之一是将您自己的委托替换为用户真正需要执行的委托。例如,假设您想捕获已排队工作中抛出的所有未处理异常并针对每个异常引发一个事件。要执行此操作,您可以编写如图 1 所示的代码。然后,使用 ExceptionThreadPool.QueueUserWorkItem 而不是使用 ThreadPool.QueueUserWorkItem。此工作仍将由 ThreadPool 来执行,但池中的线程实际执行的是您排队的委托,而非用户提供的委托。接下来在调用您的委托时将会调用用户提供的委托,而且会捕获所有异常并引发目标事件。

图 1 填充 ThreadPool

public static class ExceptionThreadPool { public static void QueueUserWorkItem(   WaitCallback callback, object state) {  ThreadPool.QueueUserWorkItem(delegate {   try { callback(state); }   catch (Exception exc) {    var handler = UnhandledException;    if (handler != null)     handler(null,      new UnhandledExceptionEventArgs(exc, false));   }  }); } public static event  UnhandledExceptionEventHandler UnhandledException;}