【原创】Delphi中Thread Error解决三例

在实际工作中遇到的多线程故障三例,虽然一切都过去,也记录下来,权当做个总结。

一个Delphi写的较老的多线程处理应用程序,数年间一直运行良好。近日突然频繁报线程错误,并且再两台不同的服务器中的错误情况也不相同,自然解决方法也不相同。

服务器1:

OS:Windows Server 2003 x86 Enterprise

错误表现:Thread Error:接收人拒绝此信号。(156)

解决过程:

使尽解数不得其门,我这个Delphi菜鸟只好向高手求助,此时“鱼鱼桌面”的罗唐大伸援手,助我度过此难,在此向他表示感谢!

鱼鱼给的解决方案:

1. 单击开始,然后单击运行。

2. 键入 regedit,然后单击确定。

3. 导航到以下项:

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\LanmanServer\Parameters

4. 在右窗格中双击 IRPStackSize 值。

注意:如果 IRPStackSize 值仍不存在,请使用以下过程创建此值:

a. 在注册表的 Parameters 文件夹中,右健单击右窗格。

b. 指向新建,然后单击 DWord 值。

c. 键入 IRPStackSize。

重要说明:因为此数值名称区分大小写,所以请完全按照其显示的形式键入“IRPStackSize”。

5. 将“基数”更改为十进制。

6. 在“数值数据”框中,键入比列出的值大的一个值。

如果使用步骤 4 中描述的步骤创建了 IRPStackSize 值,则默认值为 15。建议将此值增大 3,因此,如果以前的值为 11,则请键入 14,然后单击“确定”。

7. 关闭注册表编辑器。

8. 重新启动计算机。

处理结果:问题解决。

上述解决方案中提到的相关技术问题:

IRPStackSize 可指定 Windows 2000 Server 所使用的 I/O 请求数据包 (IRP) 中的堆栈位置的数目。对于某些传输、MAC 驱动程序或文件系统驱动程序,您可能必须增加此数目。每个堆栈为各个接收缓冲区使用 36 字节的内存。此值在以下注册表位置设置:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters

在 Windows 2000 中,IRPStackSize 的默认值是 15,范围是从 11 到 50。

警告:注册表编辑器使用不当可导致严重问题,可能需要重新安装操作系统。Microsoft 不能保证可以解决因注册表使用不当所导致的问题。使用注册表编辑器需要您自担风险。

如果该项不存在,可以使用注册表编辑器添加它。为此,请按照下列步骤操作:

单击“开始”,然后单击“运行”。

在“打开”框中,键入 regedit,然后单击“确定”。

展开以下注册表项:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters

单击“编辑”,指向“新建”,然后单击“DWORD 值”。

键入 IRPStackSize,然后按 Enter 键以命名该值。

单击“编辑”,然后单击“修改”。

在“数据值”框中,键入适用于您的网络的十六进制值(范围为从 0xb 到 0x32),然后单击“确定”。

服务器2:

OS:Windows Server 2003 R2 x86 Enterprise

错误表现:

工作子线程为单一循环线程,一段时间(几天)后线程停止工作,程序不报错,通过主界面线程对工作线程执行Suspend后程序抛出异常

1.Thread Error: 句柄无效。 (6)

解决过程:经过多方查找资料得知该问题简单的解决办法

解决方案:

原因是当初对相关线程的FreeOnTerminate属性指定为True,当线程不能继续执行工作后过后系统对线程释放,故返回该错误。

FreeOnTerminate属性改为为False,在需要线程释放的地方用Free的方法显式的释放线程即可。

处理结果:不再该出现该异常,但问题仍未得到解决,仍抛出异常,但异常已变为:

2.Thread Error: 拒绝访问。 (5)

解决过程:经过多方查找资料未能解决该问题,最后通过调试跟踪解决

解决方案:

通过网上资料得出引发该问题原因一般有两点:

1.未使用线程同步;

2.未使用CoInitialize对线程中调用的COM组件初始化套间;

实际情况是:

1.该程序的工作线程中已用Synchronize()与主线程做了同步,但问题依旧;

2.在线程的Execute方法中用了CoInitialize(nil)为Com组件初始了套间,并在线程结束处调用了CoUninitialize释放,问题得不到解决;

服务器调试过程中发现工作线程抛出另一个异常,是在每次循环过程中,写入Log日志文件时无法独占式访问所引发的;由于日志是文本文件并且不会有其他线程、进程访问,故将该独占式访问改为共享访问即可。

处理结果:问题解决。

事后思考:该故障表现无规律可循,并不是线程中每次循环均会引发此错误,而是不知程序执行多久后、不确定线程循环了数千次后出现此问题...至今对此稳定的老Delphi程序突发如此怪病慎感蹊跷...个人推测应该是更换新服务器后系统处理能力更加高效,而硬盘写入成为了瓶颈,导致文件访问未能在下次轮询前关闭,引发了IO冲突所致,以上纯属个人猜测,若有高手对此有更深入认识,欢迎留言解惑^_^

上述解决方案中提到的相关技术问题:

CoInitialize

CoInitialize是 Windows提供的API函数,用来告诉 Windows以单线程的方式创建com对象。应用程序调用com库函数(除CoGetMalloc和内存分配函数)之前必须初始化com库。

返回值S_OK : 该线程中COM库初始化成功S_FALSE 该线程中COM库已经被初始化 CoInitialize () 标明以单线程方式创建。

使用 CoInitialize 创建可以使对象直接与线程连接,得到最高的性能。

CoInitialize并不装载COM 库,它只用来初始化当前线程使用什么样的套间。使用这个函数后,线程就和一个套间建立了对应关系。线程的套间模式决定了该线程如何调用COM对象,是否需要列集等。

CoInitialize ()并不会干扰客户和服务器之间的通信,它所做的事情是让线程注册一个套间,而线程运行过程中必然在此套间。CoInitialize和CoUninitialize必须成对使用。

有关单线程或多线程中的COM库初始化

CoInitialize、CoInitializeEx都是windows的API,主要是告诉windows以什么方式为程序创建COM对象。有哪些方式呢?单线程和多线程。

CoInitialize指明以单线程方式创建。CoInitializeEx可以指定COINIT_MULTITHREADED以多线程方式创建。

创建单线程方式的COM服务器时不用考虑串行化问题,多线程COM服务器就要考虑。

在使用中,使用CoInitialize创建可使对象直接与线程连接,得到最高的性能。创建多线程对象可以直接接收所有线程的调用,不必像单线程那样需要消息排队,但却需要COM创建线程间汇集代理,这样访问效率不高。