WaitgormultipleObjects总是返回一个信号的管道读取句柄

发布于 2025-01-28 05:33:26 字数 7569 浏览 3 评论 0原文

我的问题是:如何使用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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

回眸一笑 2025-02-04 05:33:26

我找到了一种方法,可以解决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 with WaitForMultipleObjects 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 of WaitForMultipleObjects and an event has to be created using WSACreateEvent and mapped to the socket events using WSAEventSelect. This socket event is manual reset so you must call WSAResetEvent once it is serviced.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文