在Powershell中等待C#事件时阻塞导致死锁

在PowerShell中,我试图启动一个进程,并等待直到该进程退出,或者脚本从命名管道中获得信号。尽管命名管道组件可以正常工作,但是当脚本被阻止时,代码无法处理事件。这只是事件相关代码的示例:

$ErroractionPreference = "Stop"

# launch process with the appropriate args. 
$p = [System.Diagnostics.Process]::new() 
$p.StartInfo.FileName = "notepad.exe" 
$p.StartInfo.Arguments = $null 
$p.EnableRaisingEvents = $true $p.Start()

# create a task completion source
$tcs = [System.Threading.Tasks.TaskCompletionSource[Boolean]]::new()

Register-ObjectEvent -InputObject $p -EventName Exited `
    -MessageData $tcs `
    -action {
        $Event.MessageData.SetResult($true)
    } 

# wait for the program to exit 
$processTask = $tcs.Task

# this deadlocks 
$processTask.Wait()

是否存在通过PowerShell的作业系统解决该问题的正确方法,还是通过手动启动另一个等待该过程退出的C#任务来解决该问题,从而使脚本能够在发生任何一个事件后恢复运行?

作为参考,这是更完整的脚本(包括对流程事件或命名管道的等待):

$ErroractionPreference = "Stop"

#inbound pipe
$pipelistener = [System.IO.Pipes.NamedPipeServerStream]::new("testPipe",1) 

# launch process with the appropriate args.
$p = [System.Diagnostics.Process]::new()
$p.StartInfo.FileName = "notepad.exe"
$p.StartInfo.Arguments = $null
$p.EnableRaisingEvents = $true
$p.Start()

# I would normally solve this using a TaskCompletionSource
# in C#,and have it fire off the process's exited event.

# create a task completion source
$tcs = [System.Threading.Tasks.TaskCompletionSource[Boolean]]::new()

Register-ObjectEvent -InputObject $p -EventName Exited `
    -MessageData $tcs `
    -action {
        $Event.MessageData.SetResult($true)
    } 

# wait for the program to exit,or for pipe signal
try {
    $pipetask = $pipelistener.WaitForConnectionAsync()
    $processTask = $tcs.Task

    while ($true)
    {
        echo "Launched Process,waiting for sequence to complete..."

        # this works,but not instantly - the timeout appears to allow the 
        # event to process.
        #$res = [System.Threading.Tasks.Task]::WaitAny(@($processTask,$pipetask),10000)

        # this does not.
        $res = [System.Threading.Tasks.Task]::WaitAny(@($processTask,$pipetask))


        echo "Got wait result: $res"

        if ($pipetask.IsCompleted) {
            echo "Got pipe connection"
            $sr = [System.IO.StreamReader]::new($pipelistener)
            $msg = $sr.ReadToEnd()

            echo "Pipe sent: $msg"
            break
        }

        if ($processTask.IsCompleted) {
            echo "Process completed."
            break
        }
    }
}
finally {
    $pipelistener.Dispose()
}
qwweerrttyyuuiioop 回答:在Powershell中等待C#事件时阻塞导致死锁

经过更多研究,这似乎不可能-PowerShell仅在主线程中处理C#事件,即使事件的源是后台线程。

此外,PowerShell作业系统为每个作业使用进程而不是线程,因此您不能从后台作业设置“任务完成源”。

但是,从PowerShell调用的C#代码可以正常工作,并且可以生成线程。因此,为解决此问题,我编写了一个C#函数,当进程终止时,将消息发送到命名管道:

    public static void NotifyPipeOnProcessExit(
        Process processToMonitor,String pipeName
    )
    {
        // Create a pipe client.
        var client = new NamedPipeClientStream(".",pipeName,PipeDirection.Out);

        // create a process task
        var processTask = Task.Run(() => {
            processToMonitor.WaitForExit();

            using (client)
            {
                client.Connect(0);
                var sw = new StreamWriter(client);
                sw.Write("Process exitted.");
                sw.Close();
            }

        });

        return;
    }

然后我在PowerShell中简单地调用了此函数以将流程对象附加到管道:

[ThreadTool.ThreadHelper]::NotifyPipeOnProcessExit($p,$pipeName)

$pipeListener.WaitForConnection()

这也简化了代码,因为PowerShell现在仅需要监视一个事件。

也就是说,C#代码可以轻松地设置TaskCompletionSource而不是通过命名管道发送消息。

本文链接:https://www.f2er.com/3121883.html

大家都在问