1

I am having a problem with a C# 2008 windows application not finishing executing and I am trying to determine how to resolve the issue. Initially a C# 2010 console application was written to call a C# 2008 console application Which in turn calls a web service. I changed both of these applications to be windows application since I did not want the dos pop up windows.

The problem is the called C# 2008 windows application never finishes executing. The process stays in memory.

The code listed below is part of the code from the C# 2010 application.

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);
} 

I know that C# 2008 app never finishes by looking at processes in memory. In addition if I change the line of code to the following: eProcess.WaitForExit();, the application never returns to the called program.

In the C# 2008 called application, the last line of code that is executed is the following:

Environment.Exit(1);   

Thus to resolve this problem, I have the following questions:

  1. If you have recommendations on how I can change the code I listed above, would you let me know what your recommendations are?

  2. Since these 2 programs are in production right now, I am wondering if you have suggestions on how I can resolve this problem for a "bandaid" fix? Is there a way that I can just stop the C# 2008 process that is running when the C# 2010 program finishes executing? Is there a way to make the C# 2008 application kill its own process when it has finished executing? If so, can you show me code on how to solve this problem?

  3. For the long term fix, can you tell me how to determine why the C# 2008 process does not stop and how I can fix it? I would use profiler, however my company only has the professional version of visual studio 2010. thus can you tell me what your recommendations are?

ChrisF
  • 134,786
  • 31
  • 255
  • 325
user1816979
  • 511
  • 4
  • 13
  • 25
  • A single threaded C# program finishes when it reaches the end of the `Main` method. Are you sure that this happens in the C# 2008 program? Does it involve multiple threads? Can you debug the program and see what's happening inside? – Mohammad Dehghan Feb 16 '13 at 10:41
  • 1
    Sidenote: There really is no such thing as a C# 2008 or C# 2010 program, and such a classification doesn't really add any information. Both Visual Studio 2008 and 2010 can handle C# version 1.0-3.0 and .Net Framework 1.0-3.5. VS2010 can additionally handle C# 4.0 and .Net Framework 4.0. – Oskar Berggren Feb 16 '13 at 11:03
  • The C# 2008 program was written to be single threaded. When I debug the application it gets to the last statement in main method and exists. When the C# 2010 and C# 2008 application finish running, I see the C# 2008 process and 5 other processes still run in task manager. The 5 process are calls to database with linq to sql. The linq to sql does not have a 'using' statemnet. Could the call to web service not finish? How do you tell if connection to web service really finishes? – user1816979 Feb 16 '13 at 21:55

4 Answers4

3

WaitForExit(), i.e., waits indefinitely for the process it is waiting on to end, whereas WaitForExit(int milliseconds) waits for the specified duration and then times out.

From what you have written, C# 2008 program that you are launching from the C# 2010 program is never terminating. This can be due to a few reasons.

  • It could be waiting for user input.

  • It could be stuck in an infinite loop.

  • If it is multi-threaded, one of the threads might not have completed execution and that is keeping the process alive (in case the thread is not set to be a background thread).

Try running it directly from the command line to see what it is doing.

If the behaviour of the C# 2008 program is correct/as expected when executed from the command line but it is behaving differently when executed from the C# 2010 program, then verify that the arguments match under both scenarios.

You can kill a running process by using pskill. You can do something like:

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

Finally, you can debug the running program by opening the C# solution/project for it and then using the Attach to Process command, which you will find under the Debug menu bar item in Visual Studio.

Umar Farooq Khawaja
  • 3,925
  • 1
  • 31
  • 52
  • additional questions: 1. can I type pskill directly in the program like it is any other line of code? 2. To attach to a running process, I assume that I would open visual stuio 2008. From here how would know what process to atach to? 3. Could the connection to the web service not close? Is there something in the code that I am suppose to be working on that I can look for? 4. Is there a way to tell what is keeping one of the threads active? If so can, you tell me how to look for what is keeping a thread active? – user1816979 Feb 16 '13 at 22:03
0
  1. No, you cannot type pskill directly in the code. You will have to launch a process just like you are currently doing for the C# 2008 program using the System.Diagnostics.Process class.

  2. You are right, you will attach to the process using Visual Studio, whichever version the project has been created in. Once you have the program's solution open, click on Debug\Attach to Process. It will show you a list of running processes on your machine. One of the columns (usually the first one) shows the name of the executable file, which will match your C# 2008 application's name. Select the C# 2008 program in the list and click on the Attach button, after placing a breakpoint on the suspect line of code.

  3. I am not sure what you mean.

  4. Debugging is pretty much the only way you would be able to figure out what's going on.

A final thing I have just noticed on re-reading your question. You have converted the C# 2008 application to a Windows application, is that correct? That's not what you want. A Windows application has to be terminated somehow and requires interaction. You should convert both applications back to console applications and ensure that when creating the Process object to launch the C# 2008 application you set the CreateNoWindow property of the ProcessStartInfo parameter to the Process constructor as true.

So, something like:

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);
        }
    }
}

You can probably refactor this code further because I am repeating the code where I create the process object. I have used a using block to implicitly call the Dispose on the process object; that makes the code cleaner and more readable.

The key bit is setting the CreateNoWindow property of the ProcessStartInfo object, which will stop the console windows from popping up, once you have converted the applications back from Windows application to console application.

Umar Farooq Khawaja
  • 3,925
  • 1
  • 31
  • 52
  • I have one additional question, since I think the problem is some connection to the web service I am wondering what you suggest I try and/or look for? To call the web service there are two objects that are created called a 'proxy' and a 'helper' class. Thus I am wondering if I do the following, that would solve my problem: 1. When the process to access the web service has finished, I should I execute the following: a. call dispose method on proxy, helper classes, & object being passed between main program and proxy. b. set all those object listed above to null, c. Environment.Exit(1) – user1816979 Feb 16 '13 at 23:39
  • No. I don't believe that is your issue. A Windows application must be terminated. Usually you do that by something like File|Exit or Alt + F4. If you launch a Windows application programmatically, who is going to terminate it? Calling `Dispose` or setting some variable to null is merely trying to clean up the resources. It isn't going to actually cause the application to terminate. Trace how control in the C# 2008 application gets to the `Environment.Exit(1)` line of code. – Umar Farooq Khawaja Feb 16 '13 at 23:43
  • I know how the application gets to the Environment.Exit(1) line of code since I wrote it. However there must be circumstances when this line of code is not reached since it is not executed. – user1816979 Feb 17 '13 at 03:53
  • What happens when you run the C# 2008 application from the command line? Do you have to do anything to make tat program terminate? – Umar Farooq Khawaja Feb 17 '13 at 13:51
0

Here is my solution for sending ctrl-c to a process. FYI, I never got process.WaitForExit to work.

Rather than using GenerateConsoleCtrlEvent, here is how I have found to send CTRL-C to a process. FYI, in this case, I didn't ever need to find the group process 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;
                }
            }
        }
    }
}

Then, in actually running the process and sending the CTRL-C in my main app:

            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");
            }

For details, please see Redirecting standard input of console application and Windows how to get the process group of a process that is already running?

user8128167
  • 6,929
  • 6
  • 66
  • 79
-1

This works like a charm

Instead of process.WaitForExit() I use this approach:

while (!process.StandardOutput.EndOfStream)
{
    Console.WriteLine(process.StandardOutput.ReadLine());
}
Andrei
  • 42,814
  • 35
  • 154
  • 218