2

Or not!

I have a fairly simple application timer program. The program will launch a user selected (from file dialog) executable and then terminate the process after the user specified number of minutes. During testing I found that a crash occurs when I call the Process.Kill() method and the application is minimized to the system tray.

The executable in question is Frap.exe which I use frequently and is the reason I wrote the app timer in the first place. I always minimize fraps to the tray, and this is when the crash occurs.

My use of Kill() is straight forward enough...

while (true)
        {
            //keep checking if timer expired or app closed externally (ie. by user)
            if (dtEndTime <= DateTime.Now || p.HasExited)
            {
                if (!p.HasExited)
                    p.Kill();
                break;
            }
            System.Threading.Thread.Sleep(500);
        }

In searching for alternatives methods to close an external application programmatically, I found only Close() and Kill() (CloseMainWindow is not helpful to me at all). I tried using Close(), which works providing the application is minimized the tray. If the app is minimized, Close() doesn't cause a crash but the app remains open and active. One thing I noticed in a few posts posts regarding closing external applications was the comment: "Personally I'd try to find a more graceful way of shutting it down though." made in THIS thread found here at stack flow (no offense to John). Thing is, I ran across comments like that on a few sites, with no attempt at what a graceful or elegant (or crash-free!!) method might be.

Any suggestions?

The crash experienced is not consistant and I've little to offer as to details. I am unable to debug using VS2008 as I get message - cant debug crashing application (or something similar), and depending on what other programs I have running at the time, when the Kill() is called some of them also crash (also programs only running in the tray) so I'm thinking this is some sort of problem specifically related to the system tray.

Community
  • 1
  • 1
mjFeik
  • 21
  • 1
  • 2
  • Have you tried sending ALT+F4 to the application? – Daniel A. White May 06 '09 at 20:13
  • No. Honestly, I don't how to go about that. The only interprocess comms I've done is using pipes and ports(sockets) between my own apps. I'll look into that but if you have anything specific feel free... – mjFeik May 06 '09 at 20:18

3 Answers3

2

Is it possible that your code is being executed in a way such that the Kill() statement could sometimes be called twice? In the docs for Process.Kill(), it says that the Kill executes asynchronously. So, when you call Kill(), execution continues on your main thread. Further, the docs state that Kill will throw a Win32Exception if you call it on an app that is already in the process of closing. The docs state that you can use WaitForExit() to wait for the process to exit. What happens if you put a call to WaitForExit() immediately following the call to Kill(). The loop looks ok (with the break statement). Is it possible that you have code entering that loop twice?

If that's not the problem, maybe there is another way to catch that exception: Try hooking the AppDomain.CurrentDomain.UnhandledException event (currentDomain is a static member)

The problem is that Kill runs asynchronously, so if it's throwing an exception, it's occurring on a different thread. That's why your exception handler doesn't catch it. Further (I think) that an unhandled async exception (which is what I believe you have) will cause an immediate unload of your application (which is what is happening).

Edit: Example code for hooking the UnhandledExceptionEvent Here is a simple console application that demonstrates the use of AppDomain.UnhandledException:


using System;
public class MyClass
{
    public static void Main()
    {
        System.AppDomain.CurrentDomain.UnhandledException += MyExceptionHandler;
        System.Threading.ThreadPool.QueueUserWorkItem(DoWork);
        Console.ReadLine();
    }

    private static void DoWork(object state)
    {
        throw new ApplicationException("Test");
    }

    private static void MyExceptionHandler(object sender, System.UnhandledExceptionEventArgs e)
    {
        // get the message
        System.Exception exception = e.ExceptionObject as System.Exception;
        Console.WriteLine("Unhandled Exception Detected");
        if(exception != null)
            Console.WriteLine("Message: {0}", exception.Message);
        // for this console app, hold the window open until I press enter
        Console.ReadLine();
    }

}
JMarsch
  • 21,484
  • 15
  • 77
  • 125
  • Thanks... I'll have to look into hooking you mentioned - though I'm essentially clueless on how to implement that. I'll start with reading up on AppDomain... and see what I can figure out from there. I took your advice, tested with WaitForExit() and confirmed it returns true - I open a message box - but I still crash. Moreover, I also found out that if I minimize the Fraps application while its being timed, even if I bring it back up (maximize) I will crash. But no crash at all if I dont minimize. – mjFeik May 06 '09 at 22:48
  • You know -- when I initially wrote the response, I had sample code up, but goofed up and closed my editor without saving it! I threw another example into my response today. Maybe that will help with getting some diagnostic information. At the very least, I'm glad that you found a work-around. Regards. – JMarsch May 07 '09 at 16:11
1

My first thought is to put a try/catch block around the Kill() call and log the exception you get, if there is one. It might give you a clue what's wrong. Something like:

try 
{
    if(!p.HasExited)
    {
        p.Kill();
    }
    break;
}
catch(Exception ex)
{
    System.Diagnostics.Trace.WriteLine(String.Format("Could not kill process {0}, exception {1}", p.ToString(), ex.ToString()));
}
Jeff Youel
  • 653
  • 4
  • 7
  • Right, Process.Kill() can generate several possible exceptions - by looking a which one it is throwing the problem should be clear. – Justin Ethier May 06 '09 at 20:40
  • The actual code has the 'if' in a try block, I just removed it to keep the post cleaner. Thing is, I do not get anything from the catch - tried using specific exception error catches, as well as the all-inclusive 'catch{ //code }' to no avail. The crash takes out VS completely - there isn't any debugging availble, though the option is given to debug in this or another instance of VS they fail to load. Moreover, the app Kill() is being called for DOES close. I'm left to conclude that Fraps is actually causing the crash but I still need a way to handle it (ie. kill Fraps "gracefully") – mjFeik May 06 '09 at 20:57
  • Strange. I just downloaded fraps and tried "taskkill /f /im fraps.exe" from the command line to see what would happen and all kinds of apps crashed. I guess to do its magic, fraps must be hooking the apps so killing it is probably a bad thing. I suggesting sending Alt-F4 to its main window with something like the technique here http://stackoverflow.com/questions/436430/how-to-sendkeys-f12-from-current-net-form-application. – Jeff Youel May 06 '09 at 21:19
0

I dont think I should claim this to be "THE ANSWER" but its a decent 'work around'. Adding the following to lines of code...

p.WaitForInputIdle(10000);
am.hWnd = p.MainWindowHandle;

...stopped the crashing issue. These lines were placed immediately after the Process.Start() statement. Both lines are required and in using them I opened the door to a few other questions that I will be investigating over the next few days. The first line is just an up-to 10 second wait for the started process to go 'idle' (ie. finish starting). am.hWnd is a property in my AppManagement class of type IntPtr and this is the only usage of both sides of the assignment. For lack of better explaination, these two lines are analguous to a debouncing method.

I modified the while loop only slightly to allow for a call to CloseMainWindow() which seems to be the better route to take - though if it fails I then Kill() the app:

while (true)
{
   //keep checking if timer expired or app closed externally (ie. by user)
   if (dtEndTime <= DateTime.Now || p.HasExited)  {
      try {
          if (!p.HasExited)  // if the app hasn't already exitted...
          {
             if (!p.CloseMainWindow())  // did message get sent? 
             {
                if (!p.HasExited)  //has app closed yet?
                {
                   p.Kill();  // force app to exit
                   p.WaitForExit(2000);  // a few moments for app to shut down
                }
             }
             p.Close();  // free resources
          }                        
       }
      catch { // blah blah }
      break;
   }
   System.Threading.Thread.Sleep(500);
}

My initial intention for getting the MainWindowHandle was to maximize/restore an app if minimized and I might implement that in the near future. I decided to see if other programs that run like Fraps (ie, a UI but mostly run in the system tray (like messanger services such as Yahoo et al.)). I tested with XFire and nothing I could do would return a value for the MainWindowHandle. Anyways, this is a serperate issue but one I found interesting.

PS. A bit of credit to JMarsch as it was his suggestion RE: Win32Exception that actually lead me to finding this work around - as unlikely as it seems it true.

mjFeik
  • 21
  • 1
  • 2
  • Thanks! I'm glad it helped! I'm with you though, it seems like there should be a better answer. – JMarsch May 07 '09 at 16:12