65.9K
CodeProject 正在变化。 阅读更多。
Home

更改 ThreadPool 类的默认 25 个线程限制

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.62/5 (10投票s)

2003 年 6 月 15 日

8分钟阅读

viewsIcon

100842

downloadIcon

433

更改 ThreadPool 类的默认 25 个线程限制

引言

根据 Microsoft 的文档,“线程池在您首次创建 ThreadPool 类的实例时创建。线程池每个可用处理器默认限制为 25 个线程,可以使用 mscoree.h 文件中定义的 CorSetMaxThreads 来更改。”

更改 ThreadPool 类每个处理器的默认 25 个线程限制似乎只是一个简单的函数调用。但相信我,事情并没有那么简单。我已经找到了方法。但是,如果您需要超过 25 个线程,我将严重质疑应用程序的架构。线程池对于管理通常处于等待状态且工作耗时很短的线程很有用。如果您仍然想更改默认的 25 个线程限制,那么请继续。

包含头文件

我作为 VC++ 开发者遇到的第一个问题是如何在 C# 项目中包含 mscoree.h 头文件。基本上,我们不能在 C# 项目中包含头文件,因为 C# 是纯面向对象的编程语言。头文件充满了常量,这些常量无论如何都不适合。

mscorree.h 文件是什么?

基本上,MSCoree.dll 是 Microsoft .NET 运行时执行引擎,它在一个非托管环境中托管 .NET CLR(通用语言运行时),并公开通用函数。

那么主要问题仍然是:如何从托管代码(C# 应用程序)调用非托管代码(Mscoree.dll 函数)?唯一的方法是互操作到非托管代码并调用非托管接口方法来增加最大线程数。

什么是互操作?

.NET 托管应用程序可以利用现有的 COM 组件。COM 组件通过互操作层与 .NET 运行时进行交互,该层将处理托管运行时和在非托管领域运行的 COM 组件之间来回传递的消息转换的所有细节,反之亦然。嗯,您可能知道,COM 和 .NET 的编程模型差异很大。这些差异太大了,无法在此详细介绍。因此,某种形式的“差异管理器”是必不可少的。这时 COM 互操作就派上用场了。由于 COM 互操作,COM 对象可以从 .NET 对象中使用。COM 互操作提供了对现有 COM 组件的访问,而无需修改原始组件。

如何使用 C# 与 Mscoree.h 文件中定义的 COM 对象及其接口进行互操作?

C# 使用 .NET Framework 的功能来执行 COM 互操作。C# 支持

  • 创建 COM 对象。
  • 确定对象是否实现了 COM 接口。
  • 调用 COM 接口上的方法。
  • 实现可以被 COM 客户端调用的对象和接口。

以下是为 mscoree.h 文件创建 COM 互操作并将其整合在一起的步骤:

  1. 创建 COM 类包装器
  2. 声明 COM coclass
  3. 声明 COM 接口
  4. 创建 COM 对象
  5. 使用强制转换代替 QueryInterface
  6. 设置最大线程数

首先,向 C# 项目添加一个名为 ICordThread.cs 的空 C# 代码文件。

创建 COM 类包装器

在我们的 C# 代码中,要引用 Mscoree.h 文件中定义的 COM 对象和接口,我们需要在 C# 构建中包含 COM 接口的 .NET Framework 定义。根据 Microsoft 的文档,“最简单的方法是使用 TlbImp.exe(类型库导入器),它是 .NET Framework SDK 中包含的一个命令行工具。TlbImp 将 COM 类型库转换为 .NET Framework 元数据——有效地创建一个托管包装器,可以从任何托管语言调用。使用 TlbImp 创建的 .NET Framework 元数据可以通过 /R 编译器选项包含在 C# 构建中。”

不幸的是,TlbImp.exe 实用程序无法处理 mscoree 类型库中的定义。因此,(我猜)唯一可行的替代方法是使用 C# 属性在 C# 源代码中手动定义 COM 定义。一旦我们创建了 C# 源映射,我们就可以简单地编译 C# 源代码来生成 mscoree.h 文件中定义的 COM 对象的托管包装器。

声明 COM coclass

COM coclass 是一个 COM 对象。类型库中的 coclass 定义允许列出 COM 对象的接口和属性。

COM coclass 在 C# 中表示为类。这些类必须具有 ComImport 属性。这些类适用以下限制:

  • 类不能继承自任何其他类。
  • 类不能实现任何接口。
  • 类还必须有一个 Guid 属性,用于设置类的全局唯一标识符(GUID)。

将以下 coClass 声明添加到 ICordThread.cs 文件

// Declare ThreadManager as a COM coclass:
[
// CLSID_CorRuntimeHost from MSCOREE.DLL
Guid("CB2F6723-AB3A-11D2-9C40-00C04FA30A3E"),ComImport
]
class ThreadManager // Cannot have a base class or
// interface list here.
{ 
// Cannot have any members here 
// NOTE that the C# compiler will add a default constructor
// for you (no parameters).
}

ComImport 属性将类标记为外部实现的 Com 类。此类声明允许使用 C# 名称来引用 COM 类。

上面的代码声明了一个类 ThreadManager ,表示一个从 COM 导入的类,其 CLSID 为“CB2F6723-AB3A-11D2-9C40-00C04FA30A3E”。实例化一个 ThreadManager 实例会导致相应的 COM 实例化。

声明 COM 接口

COM 接口在 C# 中表示为具有 ComImport 和 Guid 属性的接口。它们不能在其基接口列表中包含任何接口,并且必须按照方法在 COM 接口中出现的顺序声明接口成员函数。

在 C# 中声明的 COM 接口必须包含其基接口的所有成员的声明,但 IUnknown 和 IDispatch 的成员除外——.NET Framework 会自动添加这些成员。

.NET Framework 默认在 .NET Framework 调用 COM 接口方法时,在两种异常处理方式之间提供自动映射。

  • 返回值更改为标记为 retval 的参数的签名(如果方法没有标记为 retval 的参数,则为 void)。
  • 标记为 retval 的参数将从方法的参数列表中省略。

任何非成功返回值都将导致抛出 System.COMException 异常。

以下代码显示了一个在 C# 中声明的 COM 接口(请注意,方法使用了 COM 错误处理方法)。

ICorThreadpool 接口在 mscoree.h 中有文档记录(仅原型),但无法从 mscoree.tlb 获取。因此,以下互操作存根使我们能够访问该接口,以便查询/控制 CLR 管理的线程池。由于我们有兴趣调整线程池配置的最大线程数,因此大多数成员实际上是无效的,并且无法以当前形式调用。

将以下代码添加到 ICordThread.cs 文件

// derives from IUnknown interface:
[
// IID_IcorThreadPool
Guid("84680D3A-B2C1-46e8-ACC2-DBC0A359159A"), 
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)
] 
public interface ICorThreadpool // Cannot list any base interfaces here 
{ 
// Note that IUnknown Interface members are NOT listed here:
void RegisterWaitForSingleObject(); // Don’t call. In correct signature
void UnregisterWait(); // Don’t call. In correct signature
void QueueUserWorkItem(); // Don’t call. In correct signature
void CreateTimer(); // Don’t call. In correct signature
void ChangeTimer(); // Don’t call. In correct signature
void DeleteTimer(); // Don’t call. In correct signature
void BindIoCompletionCallback(); // Don’t call. In correct signature
void CallOrQueueUserWorkItem(); // Dont call. In correct signature
void SetMaxThreads( uint MaxWorkerThreads, uint MaxIOCompletionThreads );
void GetMaxThreads( out uint MaxWorkerThreads, 
    out uint MaxIOCompletionThreads );
void GetAvailableThreads( out uint AvailableWorkerThreads, 
    out uint AvailableIOCompletionThreads );
}

请注意 C# 接口如何映射了错误处理情况。如果 COM 方法返回错误,C# 端将引发异常。

创建 COM 对象

COM coclass 在 C# 中表示为带有无参数构造函数的类。使用 new 运算符创建该类的实例是调用 CoCreateInstance 的 C# 等价物。使用上面定义的类,可以轻松地实例化 ThreadManager 类。

public static void Main() 
{
  // 
  // Create an instance of a COM coclass - calls
  //
  // CoCreateInstance(CB2F6723-AB3A-11D2-9C40-00C04FA30A3E, 
  //   NULL, CLSCTX_ALL, 
  //   IID_IUnknown, &f) 
  //
  // returns null on failure. 
  // 
  MSCoreeTypeLib.ThreadManager threadManager =
    new MSCoreeTypeLib.ThreadManager();
:
:
    }

使用强制转换代替 QueryInterface

C# coclass 在可以访问它实现的接口之前并没有太大用处。在 C++ 中,您将使用 IUnknown 接口上的 QueryInterface 方法来导航对象的接口。在 C# 中,您可以通过显式将 COM 对象强制转换为所需的 COM 接口来执行相同的操作。如果强制转换失败,则会抛出无效的强制转换异常。

需要强制转换,因为像 CorRuntimeHost 这样的互操作存根不能有方法,如果它们要静态地声明它们实现了 ICorThreadPool ,则需要这些方法。

MSCoreeTypeLib.ThreadManager threadManager =
new MSCoreeTypeLib.ThreadManager();
// QueryInterface for the ICorThreadPool interface:
MSCoreeTypeLib.ICorThreadpool ct = 
    (MSCoreeTypeLib.ICorThreadpool)threadManager;

获取/设置最大线程数

可以使用上述 ICorThreadpool 接口对象 ct 按如下方式调用 GetMaxThreads SetMaxThreads 方法。

获取最大线程数

如果 ICorThreadPool.GetMaxThreads 返回 25 和 25,那么总共是 50 个线程(而不是说最多有 25 个线程,其中最多 25 个可以用于 I/O)。

uint maxWorkerThreads;
uint maxIOThreads;
ct.GetMaxThreads(out maxWorkerThreads, out maxIOThreads);

设置最大线程数

maxWorkerThreads = 35;
maxIOThreads = 35;
ct.SetMaxThreads(maxWorkerThreads, maxIOThreads);

整合所有内容

此示例程序演示了如何在运行时更改 CLR 管理的线程池的最大线程数。该程序使用 COM 互操作来访问 MSCOREE。该程序利用了一个名为 ICorThreadpool 的接口,该接口由运行时实现。由于此接口在 mscoree.h 中提到,但在 mscoree.tlb 中未记录,因此使用了显式的互操作存根。有关详细信息,请参阅项目中的 ICorThreadPool.cs

首先,此应用程序使用 ICorThreadPool 接口的 GetMaxThreads 方法获取并显示最大线程数。然后,它使用 IcorThreadPool 接口的 SetMaxThreads 方法设置最大线程数。

它使用 .NET 线程池对象启动 10 个线程。在代码的最后,它再次使用 GetMaxThreads 方法获取最大线程数。这次,它应该显示与 SetMaxThreads 方法设置的值相同。

本文的主要目的不是测试 ThreadPool 类或其功能。本应用程序的唯一目的是展示如何调整 ThreadPool 类的最大线程数。

参考文献

© . All rights reserved.