我创建了一个打开COM端口并处理重叠读写操作的类。它包含两个独立的线程 - 一个读取,另一个写入数据。他们都调用OnXXX程序(例如OnRead或OnWrite)来通知完成的读或写操作。
以下是线程如何工作的简短示例:
TOnWrite = procedure (Text: string);
TWritingThread = class(TThread)
strict private
FOnWrite: TOnWrite;
FWriteQueue: array of string;
FSerialPort: TAsyncSerialPort;
protected
procedure Execute; override;
public
procedure Enqueue(Text: string);
{...}
end;
TAsyncSerialPort = class
private
FCommPort: THandle;
FWritingThread: TWritingThread;
FLock: TCriticalSection;
{...}
public
procedure Open();
procedure Write(Text: string);
procedure Close();
{...}
end;
var
AsyncSerialPort: TAsyncSerialPort;
implementation
{$R *.dfm}
procedure OnWrite(Text: string);
begin
{...}
if {...} then
AsyncSerialPort.Write('something');
{...}
end;
{ TAsyncSerialPort }
procedure TAsyncSerialPort.Close;
begin
FLock.Enter;
try
FWritingThread.Terminate;
if FWritingThread.Suspended then
FWritingThread.Resume;
FWritingThread.WaitFor;
FreeAndNil(FWritingThread);
CloseHandle(FCommPort);
FCommPort := 0;
finally
FLock.Leave;
end;
end;
procedure TAsyncSerialPort.Open;
begin
FLock.Enter;
try
{open comm port}
{create writing thread}
finally
FLock.Leave;
end;
end;
procedure TAsyncSerialPort.Write(Text: string);
begin
FLock.Enter;
try
{add Text to the FWritingThread's queue}
FWritingThread.Enqueue(Text);
finally
FLock.Leave;
end;
end;
{ TWritingThread }
procedure TWritingThread.Execute;
begin
while not Terminated do
begin
{GetMessage() - wait for a message informing about a new value in the queue}
{pop a value from the queue}
{write the value}
{call OnWrite method}
end;
end;
当您查看Close()过程时,您将看到它进入临界区,终止写入线程,然后等待它完成。 由于写入线程可以在调用OnWrite方法时将要写入的新值排入队列,因此在调用TAsyncSerialPort类的Write()过程时,它将尝试进入相同的临界区。
在这里,我们遇到了僵局。调用Close()方法的线程进入临界区,然后等待写线程被关闭,同时该线程等待临界区被释放。
我一直在思考很长一段时间,但我没有找到解决这个问题的方法。问题是我想确保在保留Close()方法时没有读/写线程存活,这意味着我不能只设置那些线程的Terminated标志并离开。
我该如何解决这个问题?也许我应该改变我异步处理串口的方法?
提前感谢您的建议。
马里乌什。
---------编辑----------
这样的解决方案怎么样?
procedure TAsyncSerialPort.Close;
var
lThread: TThread;
begin
FLock.Enter;
try
lThread := FWritingThread;
if Assigned(lThread) then
begin
lThread.Terminate;
if lThread.Suspended then
lThread.Resume;
FWritingThread := nil;
end;
if FCommPort <> 0 then
begin
CloseHandle(FCommPort);
FCommPort := 0;
end;
finally
FLock.Leave;
end;
if Assigned(lThread) then
begin
lThread.WaitFor;
lThread.Free;
end;
end;
如果我的想法是正确的,这应该消除死锁问题。不幸的是,我在写入线程关闭之前关闭了通信端口句柄。这意味着当它调用任何将comm端口句柄作为其参数之一的方法(例如Write,Read,WaitCommEvent)时,应该在该线程中引发异常。我可以确定如果我在该线程中捕获该异常,它将不会影响整个应用程序的工作吗?这个问题可能听起来很愚蠢,但我认为一些例外可能导致操作系统关闭导致它的应用程序,对吗?在这种情况下我是否需要担心?
答案 0 :(得分:6)
是的,您应该重新考虑您的方法。异步操作完全可用于消除对线程的需要。如果使用线程,则使用同步(阻塞)调用。如果你使用异步操作,那么处理一个线程中的所有东西 - 不一定是主线程,但IMO在不同的线程中进行发送和接收是没有意义的。
当然有解决同步问题的方法,但我宁愿改变设计。
答案 1 :(得分:4)
您可以从关闭中取出锁定。当它从WaitFor返回时,线程主体已经注意到它已经终止,完成了最后一个循环,并且结束了。
如果您不喜欢这样做,那么您可以在FreeAndNil之前移动设置锁定。这显然允许线程关闭机制在您应用锁之前工作(因此它不必与锁的任何东西竞争)
编辑:
(1)如果你还想关闭通信句柄,请在执行循环之后或在线程的析构函数中执行。
(2)很抱歉,但您编辑的解决方案非常糟糕。终止和等待将完全安全地完成您所需的一切。
答案 2 :(得分:2)
主要问题似乎是您将Close的全部内容放在关键部分。我几乎可以肯定(但你必须检查文档)TThread.Terminate和TThread.WaitFor可以安全地从该部分之外调用。通过将该部分拉到关键部分之外,您将解决僵局。