从一个线程调用 Writeln() 会导致另一个线程间歇性地错过从串口传入的数据吗?
Could calls to Writeln() from a thread cause another thread to intermittently miss incoming data from a serial port?
一位客户正在为我测试一个程序。他收到一个间歇性错误,其中程序无法检测到来自串行端口的传入字符,这表明外部设备已完成任务。尽我所能,不能在我的办公室、我的电脑(很多)上、在接近我的客户所拥有的测试条件下重复这个问题。但是,我的客户通过多次 WebEx 会话向我展示了这个问题。
我的程序对 PC 外部的传感器进行了大量测试。每个测试都被编程为一个线程。每次测试通过选择一个Delphi菜单项进行运行测试时,是没有问题的。线程 运行s 没有错误。成功接收来自外部设备的所有字符。
然而,一个选项是 运行 所有测试,自动,按顺序。这是程序错过(间歇性地)传入字符的地方。有一个控制器单元,每个测试 运行 按顺序进行。该控制器也是一个线程,它会依次创建、执行和销毁每个传感器测试线程,一次一个。
然而,在自动测试中发生的不同之处在于,每个传感器测试线程都会将大量文本记录到文本文件中。手动运行时没有文件I/O。
文件输出是使用旧文件 I/O 完成的,使用 writeln(f,'sometext'),它被调用了很多很多次。这可能是问题所在(非线程安全文件 I/O)?我在 Whosebug 和其他地方的阅读说是的。
因此,我用来自 Embarcadero 论坛的这段代码替换了 writeln:
procedure TBaseOperation.WriteLogData(aEventString: String);
//http://embarcadero.newsgroups.archived.at/public.delphi.language.delphi.win32/201109/11092512787.html
var
logFile: TFileStream;
writer: TStreamWriter;
begin
try
logFile := TFileStream.Create('test.txt', fmOpenWrite or fmShareDenyWrite);
except
// add code to alert user
Exit;
end;
try
logFile.Seek(0, soEnd);
writer := TStreamWriter.Create(logFile);
try
writer.WriteLine(aEventString);
finally
writer.Free;
end;
finally
logFile.Free;
end;
end;
我的思路对吗?把Writeln换成上面的代码能解决问题吗?
写入文本文件是传感器测试线程在 运行 自动执行时唯一不同的操作。
没有代码,我们无法真正提供任何特别有用的帮助。也就是说,COM 端口错误可能来自许多 个不同的来源。除了代码中可能存在的任何错误外,我发现最有可能导致问题的两个硬件问题是:
- 错误的 RS-232 驱动程序,尤其是 USB<->RS-232 适配器,会使这些设备完全不可靠,尤其是在带有 xHCI/USB3.0 芯片组的较新主板上。如果您使用的是 USB-RS-232 设备,请在 BIOS 中禁用 xHCI 并查看这是否可以解决问题。使用 FTDI 驱动程序的设备(根据我的经验)似乎是最可靠的。如果设备可以使用 FTDI 驱动程序,请获取最新的可用驱动程序,即使 OEM 不提供它(同样,在较新的主板上尤其重要)。主板 RS-232 端口和 PCI/PCIe USB2.0 卡(使用 windows 本机驱动程序!)用于 USB->RS-232 适配器应该首先用于消除其他硬件问题,理想情况下。
- 线路噪声会导致 RS-232 线路出现间歇性错误。如果您没有在端口上使用硬件握手和奇偶校验位,这会成为一个双重问题,在电气噪声环境(即:制造或高能实验室环境等)中越来越多。如果您使用 115,200kbps 和劣质的自制电缆,预计会出现问题。如果您可以以较低的波特率进行测试或使用错误检查协议(奇偶校验、握手),那么就这样做。我的系统配备了不错的电缆,即使是 38,400kbps 对于噪声环境来说也太快了,并且会导致 no-parity/no-handshake 协议出错。
忘记旧的 I/O 函数,尤其不要在多线程应用程序中使用它们。它们是为旧的单线程 DOS 和 CP/M 应用程序设计的,并且不是线程安全的。它们仍然存在只是出于兼容性原因,恕我直言,是时候将它们完全删除了。
此外,处理串行通信(或任何其他任务)的线程应避免直接大量记录到文件,这是您可以在实际计算机(甚至在 SSD 上)中做的 "slower" 事情之一,尤其是如果您 open/write/close 每次(至少,保持文件打开!)
缓冲流和异步 I/O 可能会有所帮助,但最好让它们将日志数据发送到另一个线程,该线程会在达到某个阈值时将数据写入磁盘。
一位客户正在为我测试一个程序。他收到一个间歇性错误,其中程序无法检测到来自串行端口的传入字符,这表明外部设备已完成任务。尽我所能,不能在我的办公室、我的电脑(很多)上、在接近我的客户所拥有的测试条件下重复这个问题。但是,我的客户通过多次 WebEx 会话向我展示了这个问题。
我的程序对 PC 外部的传感器进行了大量测试。每个测试都被编程为一个线程。每次测试通过选择一个Delphi菜单项进行运行测试时,是没有问题的。线程 运行s 没有错误。成功接收来自外部设备的所有字符。
然而,一个选项是 运行 所有测试,自动,按顺序。这是程序错过(间歇性地)传入字符的地方。有一个控制器单元,每个测试 运行 按顺序进行。该控制器也是一个线程,它会依次创建、执行和销毁每个传感器测试线程,一次一个。
然而,在自动测试中发生的不同之处在于,每个传感器测试线程都会将大量文本记录到文本文件中。手动运行时没有文件I/O。
文件输出是使用旧文件 I/O 完成的,使用 writeln(f,'sometext'),它被调用了很多很多次。这可能是问题所在(非线程安全文件 I/O)?我在 Whosebug 和其他地方的阅读说是的。
因此,我用来自 Embarcadero 论坛的这段代码替换了 writeln:
procedure TBaseOperation.WriteLogData(aEventString: String);
//http://embarcadero.newsgroups.archived.at/public.delphi.language.delphi.win32/201109/11092512787.html
var
logFile: TFileStream;
writer: TStreamWriter;
begin
try
logFile := TFileStream.Create('test.txt', fmOpenWrite or fmShareDenyWrite);
except
// add code to alert user
Exit;
end;
try
logFile.Seek(0, soEnd);
writer := TStreamWriter.Create(logFile);
try
writer.WriteLine(aEventString);
finally
writer.Free;
end;
finally
logFile.Free;
end;
end;
我的思路对吗?把Writeln换成上面的代码能解决问题吗?
写入文本文件是传感器测试线程在 运行 自动执行时唯一不同的操作。
没有代码,我们无法真正提供任何特别有用的帮助。也就是说,COM 端口错误可能来自许多 个不同的来源。除了代码中可能存在的任何错误外,我发现最有可能导致问题的两个硬件问题是:
- 错误的 RS-232 驱动程序,尤其是 USB<->RS-232 适配器,会使这些设备完全不可靠,尤其是在带有 xHCI/USB3.0 芯片组的较新主板上。如果您使用的是 USB-RS-232 设备,请在 BIOS 中禁用 xHCI 并查看这是否可以解决问题。使用 FTDI 驱动程序的设备(根据我的经验)似乎是最可靠的。如果设备可以使用 FTDI 驱动程序,请获取最新的可用驱动程序,即使 OEM 不提供它(同样,在较新的主板上尤其重要)。主板 RS-232 端口和 PCI/PCIe USB2.0 卡(使用 windows 本机驱动程序!)用于 USB->RS-232 适配器应该首先用于消除其他硬件问题,理想情况下。
- 线路噪声会导致 RS-232 线路出现间歇性错误。如果您没有在端口上使用硬件握手和奇偶校验位,这会成为一个双重问题,在电气噪声环境(即:制造或高能实验室环境等)中越来越多。如果您使用 115,200kbps 和劣质的自制电缆,预计会出现问题。如果您可以以较低的波特率进行测试或使用错误检查协议(奇偶校验、握手),那么就这样做。我的系统配备了不错的电缆,即使是 38,400kbps 对于噪声环境来说也太快了,并且会导致 no-parity/no-handshake 协议出错。
忘记旧的 I/O 函数,尤其不要在多线程应用程序中使用它们。它们是为旧的单线程 DOS 和 CP/M 应用程序设计的,并且不是线程安全的。它们仍然存在只是出于兼容性原因,恕我直言,是时候将它们完全删除了。 此外,处理串行通信(或任何其他任务)的线程应避免直接大量记录到文件,这是您可以在实际计算机(甚至在 SSD 上)中做的 "slower" 事情之一,尤其是如果您 open/write/close 每次(至少,保持文件打开!) 缓冲流和异步 I/O 可能会有所帮助,但最好让它们将日志数据发送到另一个线程,该线程会在达到某个阈值时将数据写入磁盘。