C# - 让 Process.Start 等待进程启动

90
我需要确保在执行一个方法之前,某个进程正在运行。
该语句是:
Process.Start("popup.exe");

你能执行一个WAIT命令或在这个值上设置延迟吗?


“Make sure” 是什么意思?你需要在 popup.exe 中运行特定的东西,对吧?如果是这样,等待它运行完毕是不够的。 - Ed Bayiates
你是否对popup.exe有控制权?也就是说,你能否向其中添加代码以向生成进程发出正在运行的信号? - Ed Bayiates
14个回答

148

你是指等待完成吗?那么使用 Process.WaitForExit

var process = new Process {
    StartInfo = new ProcessStartInfo {
        FileName = "popup.exe"
    }
};
process.Start();
process.WaitForExit();

或者,如果它是一个具有用户界面的应用程序,你需要等待它进入消息循环,你可以这样说:

process.Start();
process.WaitForInputIdle();

最后,如果以上两种情况都不适用,只需Thread.Sleep一定时间即可:

process.Start();
Thread.Sleep(1000); // sleep for one second

1
在你的第一个代码片段中,你需要在 process.WaitForExit() 之前手动调用 process.Start();否则会抛出异常。 - Bala R
10
请确保你理解 WaitForInputIdle 实际上是做什么的。http://blogs.msdn.com/b/oldnewthing/archive/2010/03/25/9984720.aspx - ta.speot.is
1
@ta.speot.is 换句话说...完全符合原帖作者的要求? - Basic
@Basic 这是一个参考网站,这个答案可能会被非原帖作者的人阅读。 - Eagle-Eye
@Eagle-Eye,我不确定我理解你的观点。ta.speoit.is(无论是Q还是A的OP)都暗示该功能未按照广告所述正常工作。我检查了文档,它似乎正好做到了OP所要求的。那么这样做会使未来的访问者发现该网站有何不便之处呢? - Basic
显示剩余2条评论

22

我也曾经需要过这个,然后我对进程的窗口标题进行了检查。如果它是你期望的那个,你就可以确定该应用正在运行。我检查的应用需要一些启动时间,这种方法对我很有效。

var process = Process.Start("popup.exe");
while(process.MainWindowTitle != "Title")
{
    Thread.Sleep(10);
}

1
你可能需要在循环中执行 proc.Refresh();,就像 @AlirezaNaghizadeh 提到的那样。 - user1127860

21

'ChrisG'的答案是正确的,但我们需要每次刷新MainWindowTitle,并最好检查是否为空……就像这样:

var proc = Process.Start("popup.exe");
while (string.IsNullOrEmpty(proc.MainWindowTitle))
{
    System.Threading.Thread.Sleep(100);
    proc.Refresh();
}

2
我们可以假设一旦命令行应用程序有一个标题,它就已经启动了吗? - Jack
1
我在 while 语句中添加了一个额外的检查:!proc.HasExited - Vangi

11

首先,我知道这篇文章略显陈旧,但仍然没有一个被公认的答案,也许我的方法可以帮助其他人:)

我采取的解决方法是:

process.Start();

while (true)
{
    try
    {
        var time = process.StartTime;
        break;
    }
    catch (Exception) {}
}

只要进程没有启动,关联var time = process.StartTime就会抛出异常。所以一旦它通过了,可以安全地假设进程正在运行并进一步处理它。我使用这种方式来等待Java进程启动,因为它需要一些时间。这样应该独立于应用程序运行的机器,而不是使用Thread.Sleep()

我知道这不是一个很干净的解决方案,但这是我能想到的唯一一个应该与性能无关的解决方案。


1
尽管这种方法看起来很不专业,但似乎是获取所需信息最直接的方式。 - Arturo Torres Sánchez
调用 process.BeginOutputReadLine()Process.BeginErrorReadLine() 之后会抛出一个异常,明确指出进程尚未完全初始化。 - sɐunıɔןɐqɐp
这可能取决于您启动的进程。对于我的Java进程来说还好,您的可能需要更多时间来完成它所需的操作。但是您仍然可以使用相同的方法,只需调用任何对您至关重要的函数即可。虽然有点hacky,但应该能够工作。 - RhodryCZ
在使用Process.Start("Excel")时无法工作;它在启动闪屏后立即进入下一行,但需要另外3秒钟才能加载整个应用程序。 - Chandraprakash

7

像其他人已经说过的那样,你的问题并不是立即显而易见的。我假设你想启动一个进程,并在“准备就绪”后执行另一个操作。

当然,“准备就绪”是关键。根据您的需求,简单等待可能足够。但是,如果您需要更强大的解决方案,可以考虑使用命名的Mutex来控制两个进程之间的控制流。

例如,在主进程中,您可以创建一个命名互斥体并启动一个线程或任务来等待。然后,您可以启动第二个进程。当该进程决定“准备就绪”时,它可以打开命名互斥体(当然,必须使用相同的名称)并向第一个进程发出信号。


这正是我在搜索时所期望找到的。谢谢。 - Nicholas Miller

5

我同意Tom的观点。此外,在执行Thread.Sleep时检查进程,可以检查正在运行的进程。类似于:

bool found = 0;
while (!found)
{
    foreach (Process clsProcess in Process.GetProcesses())
        if (clsProcess.Name == Name)
            found = true;

    Thread.CurrentThread.Sleep(1000);
}

2
while (!Process.GetProcesses().Any(p=>p.Name == myName)) { Thread.Sleep(100); } - dss539
为什么不使用 GetProcessesByName() 呢?while(Process.GetProcessesByName(myName).Length == 0) { Thread.Sleep(100); }。虽然这段代码大部分时间可行,但正确的方式是等待 n 毫秒并在未找到进程时跳出循环。 - Jack

2

你确定Start方法在子进程启动之前就返回了吗?我一直以为Start是同步启动子进程的。

如果你想等待子进程完成某种初始化,那么你需要进行进程间通信 - 参见C#中Windows的进程间通信(.NET 2.0)


2
我使用了EventWaitHandle类。在父进程中,创建一个命名的EventWaitHandle,并将事件的初始状态设置为未发信号。父进程会阻塞直到子进程调用Set方法,将事件状态改变为已发信号,如下所示。
父进程:
using System;
using System.Threading;
using System.Diagnostics;

namespace MyParentProcess
{
    class Program
    {
        static void Main(string[] args)
        {
            EventWaitHandle ewh = null;
            try
            {
                ewh = new EventWaitHandle(false, EventResetMode.AutoReset, "CHILD_PROCESS_READY");

                Process process = Process.Start("MyChildProcess.exe", Process.GetCurrentProcess().Id.ToString());
                if (process != null)
                {
                    if (ewh.WaitOne(10000))
                    {
                        // Child process is ready.
                    }
                }
            }
            catch(Exception exception)
            { }
            finally
            {
                if (ewh != null)
                    ewh.Close();
            }
        }
    }
}

子进程:
using System;
using System.Threading;
using System.Diagnostics;

namespace MyChildProcess
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                // Representing some time consuming work.
                Thread.Sleep(5000);

                EventWaitHandle.OpenExisting("CHILD_PROCESS_READY")
                    .Set();

                Process.GetProcessById(Convert.ToInt32(args[0]))
                    .WaitForExit();
            }
            catch (Exception exception)
            { }
        }
    }
}

3
最佳解决方案在这里,但您需要能够在子进程中添加一些代码。 - ViRuSTriNiTy
这是正确的解决方案,附有良好的示例代码。谢谢。 - anakic

1

为了扩展@ChrisG的想法,可以考虑使用process.MainWindowHandle并查看窗口消息循环是否响应。使用p/invoke这个Win32 api: SendMessageTimeout。 从该链接中得到:

如果函数成功,则返回值为非零。如果使用HWND_BROADCAST,则SendMessageTimeout不提供有关单个窗口超时的信息。

如果函数失败或超时,则返回值为0。要获取扩展错误信息,请调用GetLastError。如果GetLastError返回ERROR_TIMEOUT,则函数已超时。


0
这里有一个使用 System.Threading.Timer 的实现。也许对于它的目的来说有点过度了。
    private static bool StartProcess(string filePath, string processName)
    {
        if (!File.Exists(filePath))
            throw new InvalidOperationException($"Unknown filepath: {(string.IsNullOrEmpty(filePath) ? "EMPTY PATH" : filePath)}");

        var isRunning = false;

        using (var resetEvent = new ManualResetEvent(false))
        {

            void Callback(object state)
            {
                if (!IsProcessActive(processName)) return;
                isRunning = true;
                // ReSharper disable once AccessToDisposedClosure
                resetEvent.Set();
            }

            using (new Timer(Callback, null, 0, TimeSpan.FromSeconds(0.5).Milliseconds))
            {
                Process.Start(filePath);
                WaitHandle.WaitAny(new WaitHandle[] { resetEvent }, TimeSpan.FromSeconds(9));
            }
        }

        return isRunning;
    }

    private static bool StopProcess(string processName)
    {
        if (!IsProcessActive(processName)) return true;

        var isRunning = true;

        using (var resetEvent = new ManualResetEvent(false))
        {

            void Callback(object state)
            {
                if (IsProcessActive(processName)) return;
                isRunning = false;
                // ReSharper disable once AccessToDisposedClosure
                resetEvent.Set();
            }

            using (new Timer(Callback, null, 0, TimeSpan.FromSeconds(0.5).Milliseconds))
            {
                foreach (var process in Process.GetProcessesByName(processName))
                    process.Kill();
                WaitHandle.WaitAny(new WaitHandle[] { resetEvent }, TimeSpan.FromSeconds(9));
            }
        }

        return isRunning;
    }

    private static bool IsProcessActive(string processName)
    {
        return Process.GetProcessesByName(processName).Any();
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接