即使不使用 RedirectStandardError/RedirectStandardOutput,Process.WaitForExit 也会挂起

Process.WaitForExit hangs even without using RedirectStandardError/RedirectStandardOutput

提问人:Hiran Patel 提问时间:5/7/2018 最后编辑:Hiran Patel 更新时间:5/26/2020 访问量:714

问:

我们有一个服务,当服务停止/服务调用的用户停止(停止/终止服务启动的进程)时,它启动一个进程并等待进程退出。

偶尔挂起。process.waitForExit(TimeSpan)

请注意,Service 启动的进程是本机进程 (C++/CLI) 进程,而 Service 是 C# 进程。

以下是我们正在使用的代码片段

public class ApplicationProcessControl : IProcessControl
 {  
    private Process _proc;
    private const int ProcessIdleTimeout = 5000;

    public bool Start(string arguments)
    {
        if (IsAlive)
        {
            Log.TraceInfo("Application process already running. Killing it now...");
            _proc.Kill();
        }

        var eProcStarted = new Mutex(false, "Mutex111");

        _proc = new Process { EnableRaisingEvents = true, StartInfo = new ProcessStartInfo(_exePath, arguments) { RedirectStandardOutput = false,RedirectStandardError = false};
        _proc.Exited += OnProcessExited;

        _proc.Start();
        bool started;
        if(_proc == null)
        {
            Log.TraceInfo("Unable to start application process");
            started = false;
        }
        else
        {
            started = eProcStarted.WaitOne(ProcessIdleTimeout);

            if(started)
            {
                Log.TraceInfo($"Application process with id {_proc.Id} started successfully");
            }
        }
        eProcStarted.Dispose();
        return started;
    } 

    public void Kill()
    {
        _proc.Kill();
    }

    public bool WaitForProcessToExit(TimeSpan timeout)
    {
        return _proc.WaitForExit((int) timeout.TotalMilliseconds);
    }

    public event Action ProcessExited;

    private void OnProcessExited(object sender, EventArgs e)
    {
        var proc = sender as Process;

        if(proc != null)
        {
            proc.Exited -= OnProcessExited;

            if(proc.ExitCode == 0)
            {
                Log.TraceInfo("Application process exited gracefully");
            }
            else
            {
                Log.DeveloperWarning("Application process exited unexpectedly with code {0}", proc.ExitCode);
                OnProcessExited();
            }
        }
    }

    private void OnProcessExited()
    {
        Action handler = ProcessExited;
        handler?.Invoke();
    }
}

public interface IProcessControl
{
    bool IsAlive { get; }

    bool Start(string arguments);

    bool WaitForProcessToExit(TimeSpan timeout);

    void Kill();

    event Action ProcessExited;
}

public class ApplicationClientService: DisposableObject, IComponentService, ITaskControl, IUIControl,
        IDataProvider<AngleFlavors>, IApplicationCloseNotifier
{
    //...
    private readonly IProcessControl _procCtrl;

    public ApplicationClientService(IObjectProvider objPro)
    {
        //...
        _procCtrl.ProcessExited += OnApplicationProcessExited;              
    }

    public void Stop()
    {
        //...
        CleanUpAppProcess();
        //...
    }


    private void CleanUpAppProcess()
    {
        //...

        if(!_procCtrl.WaitForProcessToExit(TimeSpan.FromSeconds(5)))
        {
            _procCtrl.Kill();
        }
    }

    private void OnApplicationProcessExited()
    {
        if(!_isAppRunning)
        {
            return;
        }

        _isAppRunning = false;
        _autoLaunchRequested = false;
        RaiseApplicationClosed();
        Log.DeveloperWarning("Application process closed unexpectedly");
        Log.UserMessageApplicationClosedUnexpectedly();
        ...
    }

    protected virtual void RaiseApplicationClosed()
    {
        //AuditApplicationStop();
        //ApplicationClosed?.Invoke();
    }

}
C# C++-CLI

评论

0赞 Ciaran_McCarthy 5/14/2018
这个问题及其答案是否有可能也适用于您的问题?
0赞 Hiran Patel 5/14/2018
@Ciaran_McCarthy:因为我们没有使用其他问题中提到的标准错误或输出的重定向。因此不相关。
0赞 Ciaran_McCarthy 5/14/2018
不好意思。我看到并认为它可能与此有关。new ProcessStartInfo(_exePath, arguments) { RedirectStandardOutput ,};
1赞 Hans Passant 9/18/2018
没有众所周知的 Process.WaitForExit() 在与非无限超时一起使用时可能挂起的情况。你必须寻找一个环境问题。永远排在该列表首位的是激进的反恶意软件,这种软件将其内衣捆绑在程序员的机器上,使.exe文件似乎无处不在。
2赞 Bennie Tamir 11/1/2018
我对你的互斥锁有点困惑。您启动一个进程,然后才等待互斥锁。它不会阻止您的代码同时生成该进程的多个实例。此外,在并发 Start() 调用的情况下_proc变量将被重写。在这种情况下,您将等待该过程的第二个实例。你的使用模式是否包括此方案?

答:

0赞 FandangoOnCore 5/26/2020 #1

不知道这是否可以回答您的问题(我自己对此的问题多于答案),但是此代码:

    private void CleanUpAppProcess()
    {
        //...

        if(!_procCtrl.WaitForProcessToExit(TimeSpan.FromSeconds(5)))
        {
            _procCtrl.Kill();
        }
    }

在 Kill 命令之前调用 WaitForExit。您是否希望该过程在 5 秒内自行终止/由用户终止?正如 Bennie Zhitomirsky 在他的评论中指出的那样,互斥锁在它应该拥有的时候并不拥有(如果我正确理解了您想要实现的目标,如果没有,对不起)。IsAlive的实现情况如何?

无论如何,我为 ApplicationProcessControl 类写下了一些行。我只是用一些本机过程对其进行了一些测试,并且似乎有效(但是,我仍然不确定这是您想要实现的):

    public class ApplicationProcessControl : IProcessControl
    {
        /// <summary>
        /// Process instance variable.
        /// </summary>
        private Process _proc;
        /// <summary>
        /// The thread will try to acquire the mutex for a maximum of _mutexAcquireTimeout ms.
        /// </summary>
        private const int _mutexAcquireTimeout = 5000;
        /// <summary>
        /// Global static named mutex, seen by all threads.
        /// </summary>
        private static Mutex SpawnProcessMutex = new Mutex(false, "Mutex111");
        /// <summary>
        /// The state of the process.
        /// </summary>
        public bool IsAlive
        {
            get { return !(_proc is null) && !_proc.HasExited; }
        }
        /// <summary>
        /// Spawns a new process.
        /// </summary>
        public bool Start(string arguments)
        {
            // Try to acquire the mutex for _mutexAcquireTimeout ms.
            if (!SpawnProcessMutex.WaitOne(_mutexAcquireTimeout) || IsAlive)
            {
                // Mutex is still owned (another thread got it and is trying to run the process) 
                // OR the process is already running.
                // DO NOT start a new process.
                return false;
            }

            try
            {
                // Mutex is acquired by this thread.
                // Create a new instance of the Process class.
                _proc = new Process
                {
                    EnableRaisingEvents = true,
                    StartInfo = new ProcessStartInfo("the_process_to_be_run", arguments)
                    {
                        RedirectStandardOutput = false,
                        RedirectStandardError = false
                    }
                };
                // Subscription to the ProcessExited event.
                _proc.Exited += OnProcessExited;
                // Run the process.
                var haveAnHandle = _proc.Start();

                // *******
                // TODO: The process started but we may not have an handle to it. What to do?
                // *******

                //Log.TraceInfo($"Application process with id {_proc.Id} started successfully");
                return true;
            }
            catch (Exception) // TODO: [Catch the specific exceptions here]
            {
                // The process failed to start, still we have an instance of Process with no use.
                if (!(_proc is null))
                {
                    _proc.Dispose();
                    _proc = null;
                }
                //Log.TraceInfo("Unable to start application process");
                return false;
            }
            finally 
            {
                // Release the mutex, another thread may be waiting to acquire it.
                SpawnProcessMutex.ReleaseMutex();
            }
        }
        /// <summary>
        /// Kills the process started by the Start method.
        /// </summary>
        public void Kill()
        {
            if (IsAlive) _proc.Kill();
        }
        /// <summary>
        /// Can't see a real reason to block the thread waiting synchronously for the process to
        /// exit, we are already subscribed to the Exited event.
        /// Kept here to avoid breaking the interface contract.
        /// </summary>
        public bool WaitForProcessToExit(TimeSpan timeout)
        {
            return _proc.WaitForExit((int)timeout.TotalMilliseconds);
        }
        /// <summary>
        /// Client class consumable event to know the the process actually terminated.
        /// </summary>
        public event Action ProcessExited;
        /// <summary>
        /// Process Exited event handler.
        /// </summary>
        private void OnProcessExited(object sender, EventArgs e)
        {
            // Get a reference to the actual Process object.
            var proc = sender as Process;
            if (proc is null) return;

            proc.Exited -= OnProcessExited;

            if (proc.ExitCode == 0)
            {
                // Log.TraceInfo("Application process exited gracefully");
            }
            else
            {
                // Log.DeveloperWarning("Application process exited unexpectedly with code {0}", proc.ExitCode);
                ProcessExited?.Invoke();
            }
        }
    }