我正在尝试创建一个在后台线程上运行bat命令的小程序 - 这是有效的,但我正在尝试实现超时“安全”。即如果后台命令挂起,它将在一定时间后终止。运行代码没有问题......一旦启动,我就无法终止进程。我最终将我的代码屠宰到了这个测试程序中:
public void ExecutePostProcess(string cmd)
{
//...
WriteToDebugTextBox("Executing Threaded Post Process '" + cmd + "'");
//WriteToDebugTextBox() simply writes to a TextBox across threads with a timestamp
var t = new Thread(delegate()
{
var processInfo = new ProcessStartInfo("cmd.exe", "/c " + cmd);
processInfo.CreateNoWindow = true;
processInfo.UseShellExecute = false;
processInfo.RedirectStandardError = true;
processInfo.RedirectStandardOutput = true;
var process = Process.Start(processInfo);
process.OutputDataReceived += (object sender, DataReceivedEventArgs e) => WriteToDebugTextBox(e.Data);
process.BeginOutputReadLine();
process.WaitForExit(3000);
process.Close();
//process.Kill();
//process.CloseMainWindow();
WriteToDebugTextBox("Finished Post Process");
});
t.Start();
//...
}
目前我运行此控制台“TestApp”,如下所示:
class Program
{
static void Main(string[] args)
{
int hangTime = int.Parse(args[0]);
Console.WriteLine("This is the TestApp");
Console.WriteLine("TestApp is going to have a little 'sleep' for {0} seconds", hangTime);
Thread.Sleep(hangTime * 1000);
Console.WriteLine("Test App has woken up!");
}
}
我给了10秒的挂机时间。该过程被暂停; process.WaitForExit(3000);
所以应该放弃并在3秒后终止。然而,我的TextBox的输出总是这样:
16:09:22.175执行线程后处理'test.bat'
16:09:22.257这是TestApp
16:09:22.261 TestApp将有一点“睡眠”10秒
16:09:25.191完成后期处理
16:09:32.257测试应用已经醒了!
我尝试了来自各地的无数答案,但无济于事。我该如何正确地杀掉这个过程?
答案 0 :(得分:2)
以NT Job Object开始您的流程。在此作业上设置JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
作业限制。启动您的流程并将其分配给此作业(通常,您将创建流程暂停,将其分配给作业,然后恢复流程,以防止它在添加之前转义作业上下文)。当时间到了,杀了这份工作。这样您不仅可以杀死进程,还可以杀死您的进程产生的任何子进程,这是您当前尝试中最大的缺失部分。由于没有托管的NT Jobs API,请查看Working example of CreateJobObject/SetInformationJobObject pinvoke in .net?
您甚至可以尝试设置JOB_OBJECT_LIMIT_JOB_TIME
以确保在您杀死它之前该进程不会使CPU /资源失控。
答案 1 :(得分:1)
编辑:虽然这个答案可以解决问题,但它不是理想的解决方案
好的,不想回答我自己的问题,但是我发现在找到解决方案时没有回答会更糟,所以这里是:
Serge是对的,cmd进程是被监视/杀死的进程而不是子进程。由于一些奇怪的原因,我得到了它,终止父母将随后终止任何子流程。
所以,我找到this answer关于如何获取子进程并构建扩展类:
//requires you add a reference to System.Management + using System.Diagnostics and System.Management
public static class ProcessExtensions
{
public static IEnumerable<Process> GetChildProcesses(this Process process)
{
List<Process> children = new List<Process>();
ManagementObjectSearcher mos = new ManagementObjectSearcher(String.Format("Select * From Win32_Process Where ParentProcessID={0}", process.Id));
foreach (ManagementObject mo in mos.Get())
{
children.Add(Process.GetProcessById(Convert.ToInt32(mo["ProcessID"])));
}
return children;
}
}
我构建了这个递归方法:
private void KillChildProcesses(Process process)
{
foreach(Process childProcess in process.GetChildProcesses())
{
KillChildProcesses(childProcess);
WriteToDebugTextBox("killed process: " + childProcess.ProcessName);
childProcess.Kill();
}
}
与我的主要功能混淆了
var t = new Thread(delegate()
{
try
{
var processInfo = new ProcessStartInfo("cmd.exe", "/c " + cmd);
processInfo.CreateNoWindow = true;
processInfo.UseShellExecute = false;
processInfo.RedirectStandardError = true;
processInfo.RedirectStandardOutput = true;
using (Process process = Process.Start(processInfo))
{
process.EnableRaisingEvents = true;
process.OutputDataReceived += (object sender, DataReceivedEventArgs e) => WriteToDebugTextBox(e.Data);
process.BeginOutputReadLine();
process.WaitForExit(3000);
KillChildProcesses(process);
}
}
catch(Exception ex)
{
WriteToDebugTextBox(ex.Message);
}
WriteToDebugTextBox("Finished Post Process");
});
t.Start();
现在,一旦达到WaitForExit
超时,任何在cmd下创建的子进程都将被终止 - 这有望成为现实。
12:17:43.005执行线程后期处理&#39; test.bat&#39;
12:17:43.088这是TestApp
12:17:43.093 TestApp将会有一点睡眠&#39;持续10秒
12:17:46.127杀死进程:TestApp
12:17:46.133完成后期处理
概念证明,我现在可以将其转变为适当的应用程序。
答案 2 :(得分:1)
好的,所以我相信我已经找到了理想的解决方案,非常感谢Remus和Usr。我将之前的解决方案保留下来,因为它对于小型操作来说是相当可行的。
最大的问题是当血统链断裂时,终止所有子进程变得非常困难。即 A 创建 B , B 创建 C 但 B 结束 - A 在 C 上失去任何范围。
对于我的测试,我将我的TestApp修改成了一个相当可怕的东西,一个带有自动终止触发器的自我产生的噩梦。令人讨厌的代码是这个答案的底部,我建议任何人只看一下参考。
这个梦魇似乎唯一的答案就是通过Job Objects。我使用了来自this answer的课程(归功于Alexander Yezutov - &gt; Matt Howells - &gt;'Josh')但是必须稍微修改它才能工作(因此我发布了它的代码)。我把这个类添加到我的项目中:
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace JobManagement
{
public class Job : IDisposable
{
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
static extern IntPtr CreateJobObject(IntPtr a, string lpName);
[DllImport("kernel32.dll")]
static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, UInt32 cbJobObjectInfoLength);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CloseHandle(IntPtr hObject);
private IntPtr _handle;
private bool _disposed;
public Job()
{
_handle = CreateJobObject(IntPtr.Zero, null);
var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION
{
LimitFlags = 0x2000
};
var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
BasicLimitInformation = info
};
int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length);
Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);
if (!SetInformationJobObject(_handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, (uint)length))
{
throw new Exception(string.Format("Unable to set information. Error: {0}", Marshal.GetLastWin32Error()));
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing) { }
Close();
_disposed = true;
}
public void Close()
{
CloseHandle(_handle);
_handle = IntPtr.Zero;
}
public bool AddProcess(IntPtr processHandle)
{
return AssignProcessToJobObject(_handle, processHandle);
}
public bool AddProcess(int processId)
{
return AddProcess(Process.GetProcessById(processId).Handle);
}
}
#region Helper classes
[StructLayout(LayoutKind.Sequential)]
struct IO_COUNTERS
{
public UInt64 ReadOperationCount;
public UInt64 WriteOperationCount;
public UInt64 OtherOperationCount;
public UInt64 ReadTransferCount;
public UInt64 WriteTransferCount;
public UInt64 OtherTransferCount;
}
[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
public Int64 PerProcessUserTimeLimit;
public Int64 PerJobUserTimeLimit;
public UInt32 LimitFlags;
public UIntPtr MinimumWorkingSetSize;
public UIntPtr MaximumWorkingSetSize;
public UInt32 ActiveProcessLimit;
public UIntPtr Affinity;
public UInt32 PriorityClass;
public UInt32 SchedulingClass;
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public UInt32 nLength;
public IntPtr lpSecurityDescriptor;
public Int32 bInheritHandle;
}
[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
public IO_COUNTERS IoInfo;
public UIntPtr ProcessMemoryLimit;
public UIntPtr JobMemoryLimit;
public UIntPtr PeakProcessMemoryUsed;
public UIntPtr PeakJobMemoryUsed;
}
public enum JobObjectInfoType
{
AssociateCompletionPortInformation = 7,
BasicLimitInformation = 2,
BasicUIRestrictions = 4,
EndOfJobTimeInformation = 6,
ExtendedLimitInformation = 9,
SecurityLimitInformation = 5,
GroupInformation = 11
}
#endregion
}
将我的主要方法内容更改为:
var t = new Thread(delegate()
{
try
{
using (var jobHandler = new Job())
{
var processInfo = new ProcessStartInfo("cmd.exe", "/c " + cmd);
processInfo.CreateNoWindow = true;
processInfo.UseShellExecute = false;
processInfo.RedirectStandardError = true;
processInfo.RedirectStandardOutput = true;
using (Process process = Process.Start(processInfo))
{
DateTime started = process.StartTime;
jobHandler.AddProcess(process.Id); //add the PID to the Job object
process.EnableRaisingEvents = true;
process.OutputDataReceived += (object sender, DataReceivedEventArgs e) => WriteToDebugTextBox(e.Data);
process.BeginOutputReadLine();
process.WaitForExit(_postProcessesTimeOut * 1000);
TimeSpan tpt = (DateTime.Now - started);
if (Math.Abs(tpt.TotalMilliseconds) > (_postProcessesTimeOut * 1000))
{
WriteToDebugTextBox("Timeout reached, terminating all child processes"); //jobHandler.Close() will do this, just log that the timeout was reached
}
}
jobHandler.Close(); //this will terminate all spawned processes
}
}
catch (Exception ex)
{
WriteToDebugTextBox("ERROR:" + ex.Message);
}
WriteToDebugTextBox("Finished Post Process");
});
t.Start();
通过该方法的反馈看起来像这样(注意:它在部分方法中失去了范围,但TestApp继续运行和传播):
13:06:31.055执行线程后处理'test.bat'
13:06:31.214 24976 TestApp开始
13:06:31.226 24976现在在一秒钟内打电话让自己变得糟透了......
13:06:32.213 24976创建子进程cmd'TestApp.exe'
13:06:32.229 24976儿童成型过程
13:06:32.285 24976 TestApp将有一点“睡眠”10秒
13:06:32.336 24976创建新流程26936
13:06:32.454 20344 TestApp开始
13:06:32.500 20344现在要在一秒钟内打电话给自己制造一个可怕的混乱......
13:06:32.512 20344 !!在创建子进程以打破血统链后,我将自行终止
13:06:33.521 20344创建子进程cmd'TestApp.exe'
13:06:33.540 20344成品儿童螺纹过程
13:06:33.599 20344创建新流程24124
13:06:33.707 19848 TestApp开始
13:06:33.759 19848现在在一秒钟内打电话让自己变得糟透了......
13:06:34.540 20344 !!打败自己! PID 20344
Scope lost after here
13:06:41.139超时,终止所有子进程
请注意,PID不同,因为TestApp没有被直接调用,它正在通过CMD传递 - 我在这里极端;)
这里是TestApp,我强烈建议这只是用于参考,因为它会自动创建自己的新实例(如果有人运行它,它确实有一个'kill'参数用于清理!)。
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace TestApp
{
class Program
{
/// <summary>
/// TestApp.exe [hangtime(int)] [self-terminate(bool)]
/// TestApp.exe kill --terminate all processes called TestApp
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
int hangTime = 5; //5 second default
bool selfTerminate = true;
Process thisProcess = Process.GetCurrentProcess();
if (args.Length > 0)
{
if (args[0] == "kill")
{
KillAllTestApps(thisProcess);
return;
}
hangTime = int.Parse(args[0]);
if (args.Length > 1)
{
selfTerminate = bool.Parse(args[1]);
}
}
Console.WriteLine("{0} TestApp started", thisProcess.Id);
Console.WriteLine("{0} Now going to make a horrible mess by calling myself in 1 second...", thisProcess.Id);
if (selfTerminate)
{
Console.WriteLine("{0} !! I will self terminate after creating a child process to break the lineage chain", thisProcess.Id);
}
Thread.Sleep(1000);
ExecutePostProcess("TestApp.exe", thisProcess, selfTerminate);
if (selfTerminate)
{
Thread.Sleep(1000);
Console.WriteLine("{0} !! Topping myself! PID {0}", thisProcess.Id);
thisProcess.Kill();
}
Console.WriteLine("{0} TestApp is going to have a little 'sleep' for {1} seconds", thisProcess.Id, hangTime);
Thread.Sleep(hangTime * 1000);
Console.WriteLine("{0} Test App has woken up!", thisProcess.Id);
}
public static void ExecutePostProcess(string cmd, Process thisProcess, bool selfTerminate)
{
Console.WriteLine("{0} Creating Child Process cmd '{1}'", thisProcess.Id, cmd);
var t = new Thread(delegate()
{
try
{
var processInfo = new ProcessStartInfo("cmd.exe", "/c " + cmd + " 10 " + (selfTerminate ? "false" : "true" ));
processInfo.CreateNoWindow = true;
processInfo.UseShellExecute = false;
processInfo.RedirectStandardError = true;
processInfo.RedirectStandardOutput = true;
using (Process process = Process.Start(processInfo))
{
Console.WriteLine("{0} Created New Process {1}", thisProcess.Id, process.Id);
process.EnableRaisingEvents = true;
process.OutputDataReceived += (object sender, DataReceivedEventArgs e) => Console.WriteLine(e.Data);
process.BeginOutputReadLine();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
});
t.Start();
Console.WriteLine("{0} Finished Child-Threaded Process", thisProcess.Id);
}
/// <summary>
/// kill all TestApp processes regardless of parent
/// </summary>
private static void KillAllTestApps(Process thisProcess)
{
Process[] processes = Process.GetProcessesByName("TestApp");
foreach(Process p in processes)
{
if (thisProcess.Id != p.Id)
{
Console.WriteLine("Killing {0}:{1}", p.ProcessName, p.Id);
p.Kill();
}
}
}
}
}
答案 3 :(得分:0)
我相信你需要为Exit事件添加一个EventHandler。否则&#34; WaitForExit()&#34;可能永远不会开火。 Process.OnExited Method ()
processObject.Exited += new EventHandler(myProcess_HasExited);
一旦你有一个事件处理程序,你应该能够在(x)的时间内做一个简单的循环,在结束时你只需要向进程发送一个关闭命令。
myProcess.CloseMainWindow();
// Free resources associated with process.
myProcess.Close();
如果产生的进程最终会挂起,我不会认为你可以诚实地做些什么,因为它无法接收命令或执行命令。 (所以发送一个关闭不会工作,也不会发回事件处理程序确认)