WaitgormultipleObjects总是返回一个信号的管道读取句柄
我的问题是:如何使用waitformultipleObjects等待直到有匿名管道读取内容?
下面的代码是一个最小可复制的示例 waitformultipleObjects总是以信号为匿名管道返回 阅读句柄,事件如果管道为空。
该程序创建一个匿名管和一个工作线程,然后读取 控制台的“命令”。有两个命令:“退出”退出 程序和“写”将单个字节写入管道。
线程呼叫waitformultipleObjects等待直到管道读取句柄 发出信号,何时呼唤readfile从 管道。
问题是waitgormultipleObjects始终发出通道读取句柄的信号 事件如果管道为空。然后,这里的代码将调用ReadFile 阻止螺纹直到字节真正写入管道。明显地, 该线程仅应在WaitgormultipleObject中阻止(在真实的 应用程序,等待很多处理)。
program PipeAndThreadDemo;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, System.Classes,
WinApi.Windows;
const
INVALID_FILE_HANDLE = -1;
PIPE_CMD_NOOP = 0;
PIPE_CMD_TERMINATE = 1;
type
TPipeFd = packed record
Read : THANDLE;
Write : THANDLE;
end;
PPipeFd = ^TPipeFd;
TWorkerThread = class;
TMainThread = class
private
FWorkerThread : TWorkerThread;
FEventPipe : TPipeFd;
FThreadDone : Boolean;
procedure ThreadExecuteProc;
procedure PipeCommand(Command: BYTE);
public
constructor Create;
destructor Destroy; override;
procedure Execute;
procedure ThreadTerminateProc;
end;
TWorkerThread = class(TThread)
protected
FParentCtrl : TMainThread;
procedure Terminate;
public
destructor Destroy; override;
procedure Execute; override;
property ParentCtrl : TMainThread read FParentCtrl
write FParentCtrl;
end;
var
MainThread : TMainThread;
{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
{ TMainThread }
{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
constructor TMainThread.Create;
begin
inherited Create;
FEventPipe.Read := THANDLE(INVALID_FILE_HANDLE);
FEventPipe.Write := THANDLE(INVALID_FILE_HANDLE);
end;
{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
destructor TMainThread.Destroy;
begin
if Assigned(FWorkerThread) then begin
if not FThreadDone then begin
FWorkerThread.Terminate;
while not FThreadDone do
Sleep(0);
end;
FreeAndNil(FWorkerThread);
end;
if FEventPipe.Read <> THANDLE(INVALID_FILE_HANDLE) then begin
CloseHandle(FEventPipe.Read);
FEventPipe.Read := THANDLE(INVALID_FILE_HANDLE);
end;
if FEventPipe.Write <> THANDLE(INVALID_FILE_HANDLE) then begin
CloseHandle(FEventPipe.Write);
FEventPipe.Write := THANDLE(INVALID_FILE_HANDLE);
end;
inherited Destroy;
end;
{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TMainThread.Execute;
var
More : Boolean;
Cmd : String;
begin
if not WinApi.Windows.CreatePipe(FEventPipe.Read, FEventPipe.Write, nil, 0) then begin
WriteLn('Failed to create pipe.');
Exit;
end;
OutputDebugString(PChar(Format('Pipe.Read=%d Pipe.Write=%d ThreadID=%d',
[FEventPipe.Read,
FEventPipe.Write,
GetCurrentThreadId])));
FWorkerThread := TWorkerThread.Create(TRUE);
FWorkerThread.ParentCtrl := Self;
FWorkerThread.Start;
WriteLn('Run the program using Delphi debugger and have a look at the events window.');
More := TRUE;
while More do begin
Write('Enter command> ');
ReadLn(Cmd);
Cmd := Trim(Cmd);
if SameText(Cmd, 'Exit') then
More := FALSE
else if SameText(Cmd, 'write') then
PipeCommand(PIPE_CMD_NOOP)
else
WriteLn('Unknow command "', Cmd, '"');
end;
end;
{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TMainThread.ThreadExecuteProc;
var
Handles : array of THandle;
Status : DWORD;
Count : DWORD;
Index : DWORD;
Ch : BYTE;
BytesRead : Cardinal;
begin
OutputDebugString(PChar(Format('WorkerThread begin. ThreadID=%d',
[GetCurrentThreadID])));
Count := 1;
SetLength(Handles, Count);
Handles[0] := FEventPipe.Read;
while TRUE do begin
if Count > MAXIMUM_WAIT_OBJECTS then
raise Exception.Create('WaitForMultipleObjects failure: too many handles');
Status := WaitForMultipleObjects(Count, @Handles[0], FALSE, INFINITE);
if Status = WAIT_FAILED then begin
OutputDebugString('WaitForMultipleObjects failed');
break;
end;
if (Status >= WAIT_OBJECT_0) and (Status < (WAIT_OBJECT_0 + Count)) then begin
Index := Status - WAIT_OBJECT_0;
OutputDebugString(PChar(Format('WaitForMultipleObjects signaled Handle %d',
[Handles[Index]])));
if Index = 0 then begin // Check if pipe signaled
OutputDebugString('Calling ReadFile. Should not block...');
// <<<<===== HERE IS THE ISSUE: ReadFile block even if its handle is signaled =====>
if not ReadFile(Handles[Index], Ch, 1, BytesRead, nil) then
OutputDebugString('Pipe read failed')
else if BytesRead > 0 then begin
if Ch = PIPE_CMD_TERMINATE then begin
OutputDebugString('PIPE_CMD_TERMINATE Received');
break;
end
else if Ch = PIPE_CMD_NOOP then
OutputDebugString('PIPE_CMD_NOOP Received')
else
OutputDebugString('Unknown cmd Received');
end
else
OutputDebugString('ReadFile didn''t read anything!');
end;
end;
end;
OutputDebugString(PChar(Format('WorkerThread done. ThreadID=%d',
[GetCurrentThreadID])));
FThreadDone := TRUE;
end;
{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TMainThread.ThreadTerminateProc;
begin
PipeCommand(PIPE_CMD_TERMINATE);
end;
{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TMainThread.PipeCommand(Command: BYTE);
var
BytesWritten : Cardinal;
begin
if FEventPipe.Write <> INVALID_FILE_HANDLE then
WriteFile(FEventPipe.Write, Command, 1, BytesWritten, nil);
end;
{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
{ TWorkerThread }
{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
destructor TWorkerThread.Destroy;
begin
inherited Destroy;
end;
{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TWorkerThread.Execute;
begin
if Assigned(FParentCtrl) then
FParentCtrl.ThreadExecuteProc;
end;
{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TWorkerThread.Terminate;
begin
inherited Terminate;
if Assigned(FParentCtrl) then
FParentCtrl.ThreadTerminateProc;
end;
{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
begin
try
MainThread := TMainThread.Create;
try
MainThread.Execute;
finally
MainThread.Free;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
My question is: How to use WaitForMultipleObjects to wait until there is something the be read from an anonymous pipe?
The code below is a minimal reproducible example showing the issue with
WaitForMultipleObjects which always return as signaled an anonymous pipe
read handle, event if the pipe is empty.
The program create an anonymous pipe and a worker thread then read
a "command" from the console. There are two commands: "exit" to quit the
program and "write" to write a single byte to the pipe.
The thread call WaitForMultipleObjects to wait until the pipe read handle
is signaled and when it is, it calls ReadFile to read one byte from the
pipe.
The issue is that WaitForMultipleObjects always signal the pipe read handle
event if the pipe is empty. The code here will then call ReadFile which will
block the thread until a byte is really written to the pipe. Obviously,
the thread should only be blocked in WaitForMultipleObjects (In the real
application, the wait concern a lot of handles).
program PipeAndThreadDemo;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, System.Classes,
WinApi.Windows;
const
INVALID_FILE_HANDLE = -1;
PIPE_CMD_NOOP = 0;
PIPE_CMD_TERMINATE = 1;
type
TPipeFd = packed record
Read : THANDLE;
Write : THANDLE;
end;
PPipeFd = ^TPipeFd;
TWorkerThread = class;
TMainThread = class
private
FWorkerThread : TWorkerThread;
FEventPipe : TPipeFd;
FThreadDone : Boolean;
procedure ThreadExecuteProc;
procedure PipeCommand(Command: BYTE);
public
constructor Create;
destructor Destroy; override;
procedure Execute;
procedure ThreadTerminateProc;
end;
TWorkerThread = class(TThread)
protected
FParentCtrl : TMainThread;
procedure Terminate;
public
destructor Destroy; override;
procedure Execute; override;
property ParentCtrl : TMainThread read FParentCtrl
write FParentCtrl;
end;
var
MainThread : TMainThread;
{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
{ TMainThread }
{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
constructor TMainThread.Create;
begin
inherited Create;
FEventPipe.Read := THANDLE(INVALID_FILE_HANDLE);
FEventPipe.Write := THANDLE(INVALID_FILE_HANDLE);
end;
{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
destructor TMainThread.Destroy;
begin
if Assigned(FWorkerThread) then begin
if not FThreadDone then begin
FWorkerThread.Terminate;
while not FThreadDone do
Sleep(0);
end;
FreeAndNil(FWorkerThread);
end;
if FEventPipe.Read <> THANDLE(INVALID_FILE_HANDLE) then begin
CloseHandle(FEventPipe.Read);
FEventPipe.Read := THANDLE(INVALID_FILE_HANDLE);
end;
if FEventPipe.Write <> THANDLE(INVALID_FILE_HANDLE) then begin
CloseHandle(FEventPipe.Write);
FEventPipe.Write := THANDLE(INVALID_FILE_HANDLE);
end;
inherited Destroy;
end;
{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TMainThread.Execute;
var
More : Boolean;
Cmd : String;
begin
if not WinApi.Windows.CreatePipe(FEventPipe.Read, FEventPipe.Write, nil, 0) then begin
WriteLn('Failed to create pipe.');
Exit;
end;
OutputDebugString(PChar(Format('Pipe.Read=%d Pipe.Write=%d ThreadID=%d',
[FEventPipe.Read,
FEventPipe.Write,
GetCurrentThreadId])));
FWorkerThread := TWorkerThread.Create(TRUE);
FWorkerThread.ParentCtrl := Self;
FWorkerThread.Start;
WriteLn('Run the program using Delphi debugger and have a look at the events window.');
More := TRUE;
while More do begin
Write('Enter command> ');
ReadLn(Cmd);
Cmd := Trim(Cmd);
if SameText(Cmd, 'Exit') then
More := FALSE
else if SameText(Cmd, 'write') then
PipeCommand(PIPE_CMD_NOOP)
else
WriteLn('Unknow command "', Cmd, '"');
end;
end;
{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TMainThread.ThreadExecuteProc;
var
Handles : array of THandle;
Status : DWORD;
Count : DWORD;
Index : DWORD;
Ch : BYTE;
BytesRead : Cardinal;
begin
OutputDebugString(PChar(Format('WorkerThread begin. ThreadID=%d',
[GetCurrentThreadID])));
Count := 1;
SetLength(Handles, Count);
Handles[0] := FEventPipe.Read;
while TRUE do begin
if Count > MAXIMUM_WAIT_OBJECTS then
raise Exception.Create('WaitForMultipleObjects failure: too many handles');
Status := WaitForMultipleObjects(Count, @Handles[0], FALSE, INFINITE);
if Status = WAIT_FAILED then begin
OutputDebugString('WaitForMultipleObjects failed');
break;
end;
if (Status >= WAIT_OBJECT_0) and (Status < (WAIT_OBJECT_0 + Count)) then begin
Index := Status - WAIT_OBJECT_0;
OutputDebugString(PChar(Format('WaitForMultipleObjects signaled Handle %d',
[Handles[Index]])));
if Index = 0 then begin // Check if pipe signaled
OutputDebugString('Calling ReadFile. Should not block...');
// <<<<===== HERE IS THE ISSUE: ReadFile block even if its handle is signaled =====>
if not ReadFile(Handles[Index], Ch, 1, BytesRead, nil) then
OutputDebugString('Pipe read failed')
else if BytesRead > 0 then begin
if Ch = PIPE_CMD_TERMINATE then begin
OutputDebugString('PIPE_CMD_TERMINATE Received');
break;
end
else if Ch = PIPE_CMD_NOOP then
OutputDebugString('PIPE_CMD_NOOP Received')
else
OutputDebugString('Unknown cmd Received');
end
else
OutputDebugString('ReadFile didn''t read anything!');
end;
end;
end;
OutputDebugString(PChar(Format('WorkerThread done. ThreadID=%d',
[GetCurrentThreadID])));
FThreadDone := TRUE;
end;
{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TMainThread.ThreadTerminateProc;
begin
PipeCommand(PIPE_CMD_TERMINATE);
end;
{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TMainThread.PipeCommand(Command: BYTE);
var
BytesWritten : Cardinal;
begin
if FEventPipe.Write <> INVALID_FILE_HANDLE then
WriteFile(FEventPipe.Write, Command, 1, BytesWritten, nil);
end;
{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
{ TWorkerThread }
{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
destructor TWorkerThread.Destroy;
begin
inherited Destroy;
end;
{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TWorkerThread.Execute;
begin
if Assigned(FParentCtrl) then
FParentCtrl.ThreadExecuteProc;
end;
{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TWorkerThread.Terminate;
begin
inherited Terminate;
if Assigned(FParentCtrl) then
FParentCtrl.ThreadTerminateProc;
end;
{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
begin
try
MainThread := TMainThread.Create;
try
MainThread.Execute;
finally
MainThread.Free;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我找到了一种方法,可以解决Windows不喜欢等待管道句柄的事实:只需使用
createEvent
创建一个事件,该事件可以与waitgormultPirtipleObjects
一起使用。并将其写入管道后,将其设置为偶数。如果您还需要在插座上等待,则应使用
wsawaitformultipleevents
而不是waitformultipleObjects
,并且必须使用wsacreateeevent
创建事件,并映射到使用WSAEEVENTSELECT
的套接字事件。此套接字事件是手动重置,因此您必须在维修后调用wsaresetevent
。I've found a way to work around the fact that Windows doesn't like to wait for a pipe handle: Just create an event using
CreateEvent
which can be used withWaitForMultipleObjects
and set the even just after having written to the pipe.If you also need to wait on a socket,
WSAWaitForMultipleEvents
should be used instead ofWaitForMultipleObjects
and an event has to be created usingWSACreateEvent
and mapped to the socket events usingWSAEventSelect
. This socket event is manual reset so you must callWSAResetEvent
once it is serviced.