需要发送CTRL+;C(SIGINT)处理来自主C#WPF应用程序的对象

需要发送CTRL+;C(SIGINT)处理来自主C#WPF应用程序的对象,c#,.net,wpf,process,sigint,C#,.net,Wpf,Process,Sigint,我有一个应用程序,其中我启动了许多进程对象,将输出重定向到触发事件。这些流程应该能够无限期地运行,但我也希望能够向它们发出信号,让它们优雅地终止(例如,完成所有业务,然后结束)。出于测试目的,我使用tracert。以下是我如何创建和启动流程: //Create this.process = new Process(); this.process.StartInfo.FileName = "tracert.exe"; this.process.StartInfo.Argumen

我有一个应用程序,其中我启动了许多进程对象,将输出重定向到触发事件。这些流程应该能够无限期地运行,但我也希望能够向它们发出信号,让它们优雅地终止(例如,完成所有业务,然后结束)。出于测试目的,我使用tracert。以下是我如何创建和启动流程:

//Create
this.process = new Process();
this.process.StartInfo.FileName = "tracert.exe";
this.process.StartInfo.Arguments = "google.com";
this.process.StartInfo.UseShellExecute = false;
this.process.StartInfo.CreateNoWindow = true;
this.process.StartInfo.RedirectStandardOutput = true;
this.process.StartInfo.RedirectStandardError = true;
this.process.StartInfo.RedirectStandardInput = true;
this.process.EnableRaisingEvents = true;
this.process.OutputDataReceived += Process_OutputDataReceived;
this.process.Exited += Process_Exited;
...
//Start
new Thread(() =>
{
  Thread.CurrentThread.IsBackground = true;
  this.process.Refresh();
  this.process.Start();
  this.process.BeginOutputReadLine();
  this.process.WaitForExit();
}).Start();
根据我所读的内容,我的理解是这是发送ctrl+c信号的方式,如下所示:

[DllImport("kernel32.dll", SetLastError = true)]
static extern bool GenerateConsoleCtrlEvent(ConsoleCtrlEvent sigevent, int dwProcessGroupId);

[DllImport("kernel32.dll", SetLastError = true)]
static extern bool AttachConsole(uint dwProcessId);

[DllImport("kernel32.dll")]
static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate HandlerRoutine, bool Add);

[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern bool FreeConsole();

delegate Boolean ConsoleCtrlDelegate(uint CtrlType);

public enum ConsoleCtrlEvent
{
  CTRL_C = 0,
  CTRL_BREAK = 1,
  CTRL_CLOSE = 2,
  CTRL_LOGOFF = 5,
  CTRL_SHUTDOWN = 6
}

private void StopProcess {
  if (AttachConsole((uint)this.process.Id))
  {
    SetConsoleCtrlHandler(null, true);
    GenerateConsoleCtrlEvent(ConsoleCtrlEvent.CTRL_C, 0);
    FreeConsole();
    SetConsoleCtrlHandler(null, false);
  }
}
但这似乎不起作用。它似乎会立即触发进程的退出事件(tracert的退出代码为负),但tracert的输出继续显示到完成,然后第二次触发进程的退出事件,这一次退出代码为零。如果我调用StopProcess函数一次,然后在tracert继续工作时再次调用它,那么整个应用程序将关闭


我正在使用面向.NET5.0框架的WPF构建主应用程序。任何帮助都将不胜感激

我能够重现您报告的第二个问题,即当您尝试向控制台进程发送信号时,WPF进程将退出。(第一个问题,您已经解释过,这是看门狗代码中的一个错误,即使在明确发出退出信号时也会重新启动流程。)

经过调查,我认为这是由调用
generateConsoleControlEvent()
和随后调用
SetConsoleCtrlHandler()
之间的竞争条件引起的。如果这些调用发生得太快,则WPF应用程序中的默认处理仍然可以看到由
generateConsoleCortLevent()
发送的Ctrl+C,从而导致进程以
STATUS\u CONTROL\u exit
代码退出(即按下Ctrl+C的正常结果,但对于错误的进程)

有趣的是,关于你用来发送信号的代码,有一件事让我觉得很突出,那就是它恢复了控制台的进程状态,并按照那些状态被修改的相同顺序恢复了信号处理。这对我来说似乎很不寻常,因为人们通常以相反的顺序恢复状态,从而“退出”状态

如果更改代码,以便在释放连接的控制台之前恢复信号处理(即通常编写代码的方式),则主机进程接收信号并退出的问题会在第一次调用该方法时重现。也就是说,它第一次工作的唯一原因似乎是主机进程第一次调用
FreeConsole()
函数时有一些延迟,这足以让信号不被注意。第二次通过时,延迟不再存在(可能在p/invoke层缓存了一些东西……我没有费心去调查这一部分)

不过,在这之后,它的工作方式与按预期顺序恢复状态时的工作方式相同

无论如何

通过在目标进程实际退出之前不恢复当前进程的状态,我能够可靠地修复该问题。在我为了重现问题而必须构建的概念验证应用程序中,这相对简单,因为我已经实现了一个
TaskCompletionSource
,它是在引发
exit
事件时设置的,因此我能够将该源的
任务
传递给
StopProcess()
方法,以便它可以在恢复状态之前等待
任务

我建议您以类似的方式修复代码。请注意,您不能对
进程本身调用
WaitForExit()
,除非您是从UI线程以外的某个线程调用的,因为
进程
类使用UI线程引发
退出
事件,因此通过调用
WaitForExit()
阻止UI线程将导致死锁。您可以通过将对
StopProcess()
的整个调用放在另一个线程中来避免这种情况,但这对我来说似乎有些过分,尤其是当有一种更优雅的方式来实现整个过程时

您可以使用任何等待进程终止的机制,只要注意不要使UI线程死锁。但这是我写的代码,如果你想参考它

在window类中(注意,WPF完全崩溃,因为这里根本没有MVVM…这只是为了让基本的最小、完整的示例正常工作):

私有进程\u进程;
private TaskCompletionSource\u processTask;
私有异步无效开始按钮单击(对象发送方,路由目标)
{
startButton.IsEnabled=false;
stopButton.IsEnabled=true;
尝试
{
_流程=新流程();
_processTask=new TaskCompletionSource();
_process.StartInfo.FileName=“tracert.exe”;
_process.StartInfo.Arguments=“google.com”;
_process.StartInfo.UseShellExecute=false;
_process.StartInfo.CreateNoWindow=true;
_process.StartInfo.RedirectStandardOutput=true;
_process.StartInfo.RedirectStandardError=true;
_process.StartInfo.RedirectStandardInput=true;
_process.EnableRaisingEvents=true;
_process.OutputDataReceived+=(\uE,e)=>\u WriteLine($“stdout:\“{e.Data}\”);
_process.ErrorDataReceived+=(\uE,e)=>\u WriteLine($“stderr:\“{e.Data}\”);
_进程。已退出+=(,)=>
{
_WriteLine($“进程已退出。退出代码:{u Process.ExitCode}”);
_processTask.SetResult();
};
_process.Start();
_process.BeginOutputReadLine();
_process.BeginErrorReadLine();
等待_processTask.Task;
}
最后
{
_进程?.Dispose();
_进程=空;
_processTask=null;
startButton.IsEnabled=true;
stopButton.i已启用