【问题标题】:C# process does not stop executingC#进程不会停止执行
【发布时间】:2013-02-16 10:33:58
【问题描述】:

我遇到了一个 C# 2008 Windows 应用程序未完成执行的问题,我正在尝试确定如何解决该问题。最初,C# 2010 控制台应用程序被编写为调用 C# 2008 控制台应用程序,后者又调用 Web 服务。我将这两个应用程序都更改为 windows 应用程序,因为我不希望 dos 弹出窗口。

问题是被调用的 C# 2008 windows 应用程序永远不会完成执行。进程保留在内存中。

下面列出的代码是 C# 2010 应用程序代码的一部分。

private static Logger logger = LogManager.GetCurrentClassLogger(); 
try
{
    Process eProcess = new Process();
    strConsoleAppLocation = ConfigurationManager.AppSettings["client_location"];
    String Process_Arguments = null;
    eRPT_Process.StartInfo.UseShellExecute = false;
    eRPT_Process.StartInfo.FileName = strConsoleAppLocation;
    Process_Arguments = " 1 CLI";
    eProcess.StartInfo.Arguments = Process_Arguments;
    eProcess.Start();
    eProcess.WaitForExit(1800);
    Process_Arguments = null;
    eProcess.StartInfo.UseShellExecute = false;
    Process_Arguments = " 2 TIM";
    eProcess.StartInfo.Arguments = Process_Arguments;
    eProcess.Start();
    eProcess.WaitForExit(1800);
    eProcess.Dispose();
    Process_Arguments = null;
}
catch (Exception e)
{
    logger.Error(e.Message + "\n" + e.StackTrace);
} 

我知道 C# 2008 应用程序永远不会通过查看内存中的进程来完成。此外,如果我将代码行更改为以下代码:eProcess.WaitForExit();,则应用程序永远不会返回到被调用程序。

在 C# 2008 调用的应用程序中,执行的最后一行代码如下:

Environment.Exit(1);   

因此要解决这个问题,我有以下问题:

  1. 如果您对我如何更改上面列出的代码有建议,请告诉我您的建议是什么?

  2. 由于这 2 个程序目前正在生产中,我想知道您是否有关于如何通过“创可贴”修复解决此问题的建议?有没有办法让我在 C# 2010 程序完成执行时停止正在运行的 C# 2008 进程?有没有办法让 C# 2008 应用程序在完成执行后杀死自己的进程?如果是这样,你能告诉我如何解决这个问题的代码吗?

  3. 对于长期修复,您能否告诉我如何确定为什么 C# 2008 进程不会停止以及如何修复它?我会使用 profiler,但是我的公司只有 Visual Studio 2010 的专业版。所以你能告诉我你的建议是什么吗?

【问题讨论】:

  • 单线程 C# 程序在到达 Main 方法的末尾时结束。您确定这发生在 C# 2008 程序中吗?它是否涉及多个线程?你能调试一下程序看看里面发生了什么吗?
  • 旁注:确实没有 C# 2008 或 C# 2010 程序这样的东西,这样的分类并没有真正添加任何信息。 Visual Studio 2008 和 2010 都可以处理 C# 版本 1.0-3.0 和 .Net Framework 1.0-3.5。 VS2010 还可以处理 C# 4.0 和 .Net Framework 4.0。
  • C# 2008 程序被编写为单线程。当我调试应用程序时,它会到达 main 方法中的最后一条语句并存在。当 C# 2010 和 C# 2008 应用程序完成运行时,我看到 C# 2008 进程和其他 5 个进程仍在任务管理器中运行。 5 个过程是使用 linq to sql 调用数据库。 linq to sql 没有“使用”状态。对 Web 服务的调用不能完成吗?如何判断与 Web 服务的连接是否真的完成?

标签: c# process


【解决方案1】:

WaitForExit(),即无限期等待它等待结束的进程,而WaitForExit(int milliseconds) 等待指定的持续时间然后超时。

根据您编写的内容,您从 C# 2010 程序启动的 C# 2008 程序永远不会终止。这可能是由几个原因造成的。

  • 它可能正在等待用户输入。

  • 可能会陷入无限循环。

  • 如果它是多线程的,则其中一个线程可能尚未完成执行,这会使进程保持活动状态(以防线程未设置为后台线程)。

    李>

尝试直接从命令行运行它,看看它在做什么。

如果 C# 2008 程序的行为在从命令行执行时是正确的/符合预期,但在从 C# 2010 程序执行时行为不同,则验证两种情况下的参数是否匹配。

您可以使用pskill 终止正在运行的进程。您可以执行以下操作:

if (!process.WaitForExit(1800))
{
    // launch a process for pskill to kill the C# 2008 program
}

最后,您可以通过打开 C# 解决方案/项目,然后使用 Attach to Process 命令来调试正在运行的程序,您可以在 Visual Studio 的 Debug 菜单栏项下找到该命令。

【讨论】:

  • 其他问题: 1. 我可以直接在程序中输入 pskill,就像输入任何其他代码行一样吗? 2.要附加到正在运行的进程,我假设我会打开visual stuio 2008。从这里如何知道要附加到哪个进程? 3. 无法关闭与 Web 服务的连接?我想在代码中是否有一些我可以寻找的东西? 4. 有没有办法知道是什么让其中一个线程保持活动状态?如果可以的话,你告诉我如何查找保持线程处于活动状态的原因?
【解决方案2】:
  1. 不,您不能直接在代码中键入 pskill。您将不得不启动一个进程,就像您目前使用 System.Diagnostics.Process 类为 C# 2008 程序所做的那样。

  2. 您是对的,您将使用 Visual Studio 附加到进程,无论项目已在哪个版本中创建。打开程序的解决方案后,单击 Debug\Attach to Process。它将显示您机器上正在运行的进程的列表。其中一列(通常是第一列)显示可执行文件的名称,它将与您的 C# 2008 应用程序的名称相匹配。在列表中选择 C# 2008 程序并在可疑代码行上放置断点后单击 Attach 按钮。

  3. 我不确定你的意思。

  4. 调试几乎是您能够弄清楚发生了什么的唯一方法。

我刚刚在重新阅读您的问题时注意到的最后一件事。您已将 C# 2008 应用程序转换为 Windows 应用程序,对吗?那不是你想要的。 Windows 应用程序必须以某种方式终止并需要交互。您应该将这两个应用程序转换回控制台应用程序,并确保在创建 Process 对象以启动 C# 2008 应用程序时,将 ProcessStartInfo 参数的 CreateNoWindow 属性设置为 Process 构造函数为 true。

所以,类似:

public class Processor
{
    private static Logger logger = LogManager.GetCurrentClassLogger();

    private ProcessStartInfo MakeProcessStartInfo(string fileName, string arguments)
    {
        return new ProcessStartInfo
        {
            CreateNoWindow = true,
            UseShellExecute = false,
            FileName = fileName,
            Arguments = appArguments
        };
    }

    public void CallExternalApplications()
    {
        try
        {
            var fileName = ConfigurationManager.AppSettings["client_location"];

            using (var process = new Process { StartInfo = MakeProcessStartInfo(fileName, " 1 CLI") })
            {
                process.Start();

                if (!process.WaitForExit(1800))
                {
                    // create a Process here for pskill to kill the "process" using process.Id.
                }
            }

            using (var process = new Process { StartInfo = MakeProcessStartInfo(fileName, " 2 TIM") })
            {
                process.Start();

                if (!process.WaitForExit(1800))
                {
                    // create a Process here for pskill to kill the "process" using process.Id.
                }
            }
        }
        catch (Exception e)
        {
            // you really should be using logger.ErrorException(e.Message, e) here
            // and be using the ${exception} layoutrenderer in the layout in the NLog.config file
            logger.Error(e.Message + "\n" + e.StackTrace);
        }
    }
}

您可能可以进一步重构此代码,因为我正在重复创建process 对象的代码。我使用了using 块来隐式调用process 对象上的Dispose;这使代码更清晰,更具可读性。

关键位是设置ProcessStartInfo 对象的CreateNoWindow 属性,一旦您将应用程序从Windows 应用程序转换回控制台应用程序,这将阻止控制台窗口弹出。

【讨论】:

  • 我还有一个问题,因为我认为问题是与 Web 服务的某种连接我想知道您建议我尝试和/或寻找什么?要调用 Web 服务,需要创建两个对象,称为“代理”和“助手”类。因此,我想知道如果我执行以下操作,是否可以解决我的问题: 1. 当访问 Web 服务的过程完成后,我应该执行以下操作:在主程序和代理之间传递的代理、助手类和对象上调用 dispose 方法。湾。将上面列出的所有对象设置为 null,c。 Environment.Exit(1)
  • 没有。我不相信这是你的问题。必须终止 Windows 应用程序。通常,您可以通过 File|Exit 或 Alt + F4 来执行此操作。如果您以编程方式启动 Windows 应用程序,谁来终止它?调用Dispose 或将某个变量设置为null 只是试图清理资源。它实际上不会导致应用程序终止。跟踪 C# 2008 应用程序中的控制如何到达 Environment.Exit(1) 代码行。
  • 我知道应用程序是如何到达 Environment.Exit(1) 代码行的,因为我编写了它。但是一定有这样的情况,因为这行代码没有执行,所以没有到达。
  • 从命令行运行 C# 2008 应用程序时会发生什么?您是否需要做任何事情才能使 tat 程序终止?
【解决方案3】:

这是我将 ctrl-c 发送到进程的解决方案。仅供参考,我从来没有让process.WaitForExit 工作。

不是使用 GenerateConsoleCtrlEvent,而是我发现将 CTRL-C 发送到进程的方法。仅供参考,在这种情况下,我不需要找到组进程 ID。

using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

public class ConsoleAppManager
{
    private readonly string appName;
    private readonly Process process = new Process();
    private readonly object theLock = new object();
    private SynchronizationContext context;
    private string pendingWriteData;

    public ConsoleAppManager(string appName)
    {
        this.appName = appName;

        this.process.StartInfo.FileName = this.appName;
        this.process.StartInfo.RedirectStandardError = true;
        this.process.StartInfo.StandardErrorEncoding = Encoding.UTF8;

        this.process.StartInfo.RedirectStandardInput = true;
        this.process.StartInfo.RedirectStandardOutput = true;
        this.process.EnableRaisingEvents = true;
        this.process.StartInfo.CreateNoWindow = true;

        this.process.StartInfo.UseShellExecute = false;

        this.process.StartInfo.StandardOutputEncoding = Encoding.UTF8;

        this.process.Exited += this.ProcessOnExited;
    }

    public event EventHandler<string> ErrorTextReceived;
    public event EventHandler ProcessExited;
    public event EventHandler<string> StandartTextReceived;

    public int ExitCode
    {
        get { return this.process.ExitCode; }
    }

    public bool Running
    {
        get; private set;
    }

    public void ExecuteAsync(params string[] args)
    {
        if (this.Running)
        {
            throw new InvalidOperationException(
                "Process is still Running. Please wait for the process to complete.");
        }

        string arguments = string.Join(" ", args);

        this.process.StartInfo.Arguments = arguments;

        this.context = SynchronizationContext.Current;

        this.process.Start();
        this.Running = true;

        new Task(this.ReadOutputAsync).Start();
        new Task(this.WriteInputTask).Start();
        new Task(this.ReadOutputErrorAsync).Start();
    }

    public void Write(string data)
    {
        if (data == null)
        {
            return;
        }

        lock (this.theLock)
        {
            this.pendingWriteData = data;
        }
    }

    public void WriteLine(string data)
    {
        this.Write(data + Environment.NewLine);
    }

    protected virtual void OnErrorTextReceived(string e)
    {
        EventHandler<string> handler = this.ErrorTextReceived;

        if (handler != null)
        {
            if (this.context != null)
            {
                this.context.Post(delegate { handler(this, e); }, null);
            }
            else
            {
                handler(this, e);
            }
        }
    }

    protected virtual void OnProcessExited()
    {
        EventHandler handler = this.ProcessExited;
        if (handler != null)
        {
            handler(this, EventArgs.Empty);
        }
    }

    protected virtual void OnStandartTextReceived(string e)
    {
        EventHandler<string> handler = this.StandartTextReceived;

        if (handler != null)
        {
            if (this.context != null)
            {
                this.context.Post(delegate { handler(this, e); }, null);
            }
            else
            {
                handler(this, e);
            }
        }
    }

    private void ProcessOnExited(object sender, EventArgs eventArgs)
    {
        this.OnProcessExited();
    }

    private async void ReadOutputAsync()
    {
        var standart = new StringBuilder();
        var buff = new char[1024];
        int length;

        while (this.process.HasExited == false)
        {
            standart.Clear();

            length = await this.process.StandardOutput.ReadAsync(buff, 0, buff.Length);
            standart.Append(buff.SubArray(0, length));
            this.OnStandartTextReceived(standart.ToString());
            Thread.Sleep(1);
        }

        this.Running = false;
    }

    private async void ReadOutputErrorAsync()
    {
        var sb = new StringBuilder();

        do
        {
            sb.Clear();
            var buff = new char[1024];
            int length = await this.process.StandardError.ReadAsync(buff, 0, buff.Length);
            sb.Append(buff.SubArray(0, length));
            this.OnErrorTextReceived(sb.ToString());
            Thread.Sleep(1);
        }
        while (this.process.HasExited == false);
    }

    private async void WriteInputTask()
    {
        while (this.process.HasExited == false)
        {
            Thread.Sleep(1);

            if (this.pendingWriteData != null)
            {
                await this.process.StandardInput.WriteLineAsync(this.pendingWriteData);
                await this.process.StandardInput.FlushAsync();

                lock (this.theLock)
                {
                    this.pendingWriteData = null;
                }
            }
        }
    }
}

然后,在我的主应用程序中实际运行进程并发送 CTRL-C:

            DateTime maxStartDateTime = //... some date time;
            DateTime maxEndDateTime = //... some later date time
            var duration = maxEndDateTime.Subtract(maxStartDateTime);
            ConsoleAppManager appManager = new ConsoleAppManager("myapp.exe");
            string[] args = new string[] { "args here" };
            appManager.ExecuteAsync(args);
            await Task.Delay(Convert.ToInt32(duration.TotalSeconds * 1000) + 20000);

            if (appManager.Running)
            {
                // If stilll running, send CTRL-C
                appManager.Write("\x3");
            }

详情请见Redirecting standard input of console applicationWindows how to get the process group of a process that is already running?

【讨论】:

    【解决方案4】:

    这就像一个魅力

    我使用这种方法而不是process.WaitForExit()

    while (!process.StandardOutput.EndOfStream)
    {
        Console.WriteLine(process.StandardOutput.ReadLine());
    }
    

    【讨论】: