Avoid locking service if process crashes / hangs - c#

I am currently developing a windows service which implements fileSystemWatcher. Videos are uploaded into a folder at which point the filewatcher fires the created event as below to convert the video.
private void fileSystemWatcher_Created(object sender, System.IO.FileSystemEventArgs e)
{
if (ConvertVideo(e.FullPath, e.Name))
{
WriteToEventLog(String.Format("Successfully converted video - {0}", e.FullPath), EventLogEntryType.Information);
}
}
Within ConvertVideo a new process is created but I have run into issues where the process crashes / hangs / disappears and it appears the main thread is then locked as its waiting for WaitForExit() which effectively crashes the service as no other videos can then be converted. How could I avoid locking the entire service if the process dies?
private bool ConvertVideo(string SourcePath, string Filename)
{
try
{
// Create new process
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.CreateNoWindow = false;
startInfo.UseShellExecute = false;
startInfo.FileName = "C:\Handbrake\HandBrakeCLI.exe";
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
startInfo.Arguments = GetArguments(SourcePath, Filename);
int? exitCode = null;
using (Process exeProcess = Process.Start(startInfo))
{
exeProcess.WaitForExit();
exitCode = exeProcess.ExitCode;
}
}
catch(Exception ex)
{
return false;
}
}
NOTE: Code is shortened for this example

According to MSDN, Process.WaitForExit should return if your process crashes (emphasis added):
When an associated process exits (that is, when it is shut down by the
operation system through a normal or abnormal termination), the system
stores administrative information about the process and returns to the
component that had called WaitForExit().
It appears that your HandBrake process is just hanging and staying alive. The best solution would be to debug that process and figure out where it is crashing, but not closing down. Do you have access to the HandBrakeCLI.exe code?
If you don't have access to the HandBrake.exe code: you could use Process.WaitForExit(Int32) to set a timeout. If the timeout is reached, you may want to manually kill your process via the Process.Kill function, or all subsequent calls to Process.Start(ProcessStartInfo) will not work properly, since they will only return a new process if the process wasn'talready running:
A new Process component that is associated with the process resource,
or null if no process resource is started (for example, if an existing
process is reused).

1)
You should spawn your process and wait for it to terminate in a separate thread, in order to avoid blocking your main thread.
2)
You could use the WaitForExit method that takes the max time to wait for the process as a parameter. You'll then be able to avoid the case a thread of your program is blocked forever.

Related

Batch script calling robocopy in Process won't terminate

If process.Kill() is called from another thread or even another program, the process never comes out of WaitForExit() if the batch script used robocopy.exe until it is finished as if it wasn't killed.
Robocopy.exe is called from the batch script. Every other script or program ends as you'd expect.
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = "batch.bat";
startInfo.UseShellExecute = false;
startInfo.CreateNoWindow = true;
startInfo.RedirectStandardOutput = true;
startInfo.OutputDataReceived += CaptureHandler;
startInfo.RedirectStandardError = true;
startInfo.ErrorDataReceived += CaptureHandler;
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
The batch script looks like:
#echo off
call "robocopy.exe" "somedir" "somedest" /mir /fp /ndl /njh /njs /ns
I have a feeling it has to do with the output handlers.
I tried using process.CancelErrorRead and process.CancelOutputRead() as well after the Kill() call and before, no luck.
Oddly, if you use process.WaitForExit(timeout) overload, it will return true immediately after Kill() from the other thread. However, it's lying. The process is still running! If you try process.WaitForExit() again, as per the MSDN doc, it will still wait for the process to finish despite HasExited saying true.
To ensure that asynchronous event handling has been completed, call the WaitForExit() overload that takes no parameter after receiving a true from this overload.
https://msdn.microsoft.com/en-us/library/ty0d8k56(v=vs.110).aspx
You are successfully killing the batch processor (cmd.exe) but doing so won't kill robocopy, which is a separate process.
It doesn't seem to be documented, but when we look at the .NET source code it turns out that the Process.WaitForExit() method doesn't just wait for the process to exit, it also waits for end-of-file on the standard output and standard error streams. In this scenario, that means that it waits for robocopy to finish even after the batch processor has been killed.
(The overload of Process.WaitForExit with a timeout does not have this extra logic.)
I think this constitutes a bug in the .NET framework. At the very least, it should be documented.
As a workaround, you can use .HasExited and/or the version of WaitForExit with a timeout to determine whether the process has exited or not. Of course, in your scenario you might prefer to wait for grandchild processes, in which case your code is already behaving as desired.
I ran into the same problem. In my case, dropping the /mt switch from the RoboCopy argument list seemed to fix the issue.
Having followed up on Harry Johnston's helpful answer, I found that the process completes normally when you avoid RedirectStandardOutput = true. If this isn't an acceptable solution I found that using robocopy's /LOG:"C:\logs\robocopy.txt" switch to send its standard output to an external log file also works (although you lose the ability to get the file/directory log output from the process object itself).
Looks like right now the only way to do this without the application knowing to terminate Robocopy.exe specifically is to do kill the children of the script process before killing the script itself:
Kill process tree programmatically in C#
/// <summary>
/// Kill a process, and all of its children, grandchildren, etc.
/// </summary>
/// <param name="pid">Process ID.</param>
private static void KillProcessAndChildren(int pid)
{
ManagementObjectSearcher searcher = new ManagementObjectSearcher
("Select * From Win32_Process Where ParentProcessID=" + pid);
ManagementObjectCollection moc = searcher.Get();
foreach (ManagementObject mo in moc)
{
KillProcessAndChildren(Convert.ToInt32(mo["ProcessID"]));
}
try
{
Process proc = Process.GetProcessById(pid);
proc.Kill();
}
catch (ArgumentException)
{
// Process already exited.
}
}

Running install process from c# wpf application

In my C# WPF Application in statup process I'm checking whether the installation of new version is required. If so, I want to break current process and start installer. The installer is developed using NSIS package.
The problem is that sometimes only User Account Control dialog from NSIS installer appears and install process breaks.
How to ensure that the installation process is executed every time?
Here is my code on Application Startup.
protected override void OnStartup(StartupEventArgs e)
{
try
{
//Disable shutdown when the dialog closes
Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;
if ( IfUpdateRequired())
{
System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo(sessionCtx.AutoUpdateVersionInfo.SetupPath);
//This should not block current program
startInfo.UseShellExecute = true;
startInfo.Verb = "runas";
System.Diagnostics.Process.Start(startInfo);
this.Shutdown();
}
else
{
base.OnStartup(e);
}
}
catch (Exception ex)
{
}
}
My only guess is that the child process has not fully started before your process quits. ShellExecute is allowed to perform its operation asynchronously.
If this is the cause then you should be able to work around it by sleeping a bit before calling this.Shutdown(). Wait 10 seconds or so perhaps? Or call WaitForInputIdle(9999) on the process. Or maybe you could check the Responding process property?

How to wait until MSTSC.exe exits

I have created a management application that also allows to quickly access a remote desktop session to remote machines. I need to wait until the process ends, so I can close the VPN connection to the remote server. Everything works fine, except waiting for the process to end.
The following code is being used to start the MSTSC process and wait until it ends:
var process = new Process
{
StartInfo = new ProcessStartInfo("mstsc.exe"),
EnableRaisingEvents = true
};
process.Exited += (o, e) => Console.WriteLine("Process stopped.");
process.Start();
Console.ReadLine();
The Exited event is raised almost immediately after the program starts. When I replace mstsc.exe with notepad.exe everything works as expected. I thought that MSTSC might fork itself and abort the initial process.
But it is possible to wait for MSTSC to end using the following command (from the commandline):
start /wait mstsc.exe
This command doesn't return until I exit the remote desktop session. Given that information I replaced my code with this:
var process = new Process
{
StartInfo = new ProcessStartInfo("cmd.exe"),
Arguments = "/c start /wait mstsc.exe",
EnableRaisingEvents = true
};
process.Exited += (o, e) => Console.WriteLine("Process stopped.");
process.Start();
Console.ReadLine();
This would run CMD.exe and it will issue the start /wait mstsc.exe command. If that ends, the CMD process ends as well and I'm fine (with a nasty workaround, but okay). Unfortunately, this doesn't happen. The CMD process terminates immediately. Somebody knows what I am doing wrong?
process.WaitForExit();
Won't work because mstsc on start opens new copy of itself and closes original.
process.WaitForExit();
process = Process.GetProcessesByName(process.ProcessName).First();
process.WaitForExit();
Will work but it's awful workaround.
Update 1:
It seems that mstsc closes original process but NOT it's output stream!
So you can wait for process StandardOutput to close.
var process = new Process
{
StartInfo = new ProcessStartInfo("mstsc.exe") { UseShellExecute = false, RedirectStandardOutput = true }
};
process.Start();
process.StandardOutput.ReadToEnd(); //This will wait for stream to close.
Or if you don't want to block current thread:
var process = new Process
{
StartInfo = new ProcessStartInfo("mstsc.exe") { UseShellExecute = false, RedirectStandardOutput = true }
};
process.Start();
var outputResultPromise = process.StandardOutput.ReadToEndAsync();
outputResultPromise.ContinueWith(o=> Console.WriteLine("Stream closed"));
Console.ReadLine();
Here is the link at MSDN about starting mstsc,
It might be answer to your problem with mstsc closing immediately after running (raising Exited event). Try changing in Visual Studio target platform to AnyCPU.
Let's say your machine is 64bit Windows, your app is 32bit. The app runs 32bit mstsc. 32bit mstsc detects that Windows is 64bit, tries to close itself and run 64bit mstsc (Exited event is raised at that moment even though mstsc starts GUI window).
Changing target platform solved my issue.
There are multiple MSTSC processes running, so it's difficult to wait for one. What I don't understand is that CMD.EXE can do it when I use the start /wait command.
this worked with me:
process.Start();
Thread.Sleep(2000);
while(getNumProcesses() > 0)
process.WaitForExit();
private static int getNumProcesses()
{
Process[] myProcesses = Process.GetProcessesByName("mstsc");
return myProcesses.Length;
}
You cannot wait for mstsc.exe process. Say exactly, you cannot simply wait for end of remote desktop. When I observed mstsc.exe process by Process Monitor, mstsc passed his work to svchost, mstsc.exe ended, but remote desktop was still run.
But I wrote script for remoting application.
Script remoteCmd.cmd starts remoteApplication, remote machine creates a temp file ( \\tsclient\c..\temp\xxx) and remoteCmd.cmd waits until temp file exists.
See
https://github.com/turzik/WindowsScripts/tree/master/remoteApp
You need to call WaitForExit() after you call Start():
process.Start();
process.WaitForExit();
This overload causes the current thread to wait indefinitely to wait until the process exits. There's also an overload that allows you to specify the number of milliseconds you'd like to wait.

Start process and allow caller to end without waiting for process to finish

I need to start a process from a C# console app and then allow the console app to finish/end without waiting for the process/thread to finish.
How do I do this?
You need to avoid making your new process a child process of the current process:
ProcessStartInfo sinfo = new ProcessStartInfo();
sinfo.UseShellExecute = true; // Do not wait - make the process stand alone
sinfo.FileName = "PathAndNameofExe";
Process.Start(sinfo);
Process.Start("TheNameOfTheOtherProcess.exe");

Restarting current process C#

I have an app that has some installer inside I want to reload everything associated to the app therefor I want to restart the process. I've searched and saw the Application.Restart() and it's drawbacks and wondered what's the best way to do what I need - closing the process and restarting it. or if there's any better way to reinitialize all objects.
I would start a new instance and then exit the current one:
private void Restart()
{
Process.Start(Application.ExecutablePath);
//some time to start the new instance.
Thread.Sleep(2000);
Environment.Exit(-1);//Force termination of the current process.
}
private static void Main()
{
//wait because we maybe here becuase of the system is restarted so give it some time to clear the old instance first
Thread.Sleep(5000);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(...
}
Edit: However you should also consider adding some sort of mutex to allow only one instance of the application to run at time, Like:
private const string OneInstanceMutexName = #"Global\MyUniqueName";
private static void Main()
{
Thread.Sleep(5000);
bool firstInstance = false;
using (System.Threading.Mutex _oneInstanceMutex = new System.Threading.Mutex(true, OneInstanceMutexName, out firstInstance))
{
if (firstInstance)
{
//....
}
}
}
In my WPF application (single instance by a mutex), I use Process.Start with a ProcessStartInfo, which send a timed cmd command to restart the app:
ProcessStartInfo Info = new ProcessStartInfo();
Info.Arguments = "/C ping 127.0.0.1 -n 2 && \"" + Application.GetCurrentProcess()+ "\"";
Info.WindowStyle = ProcessWindowStyle.Hidden;
Info.CreateNoWindow = true;
Info.FileName = "cmd.exe";
Process.Start(Info);
ShellView.Close();
The command is sent to the OS, the ping pauses the script for 2-3 seconds, by which time the application has exited from ShellView.Close(), then the next command after the ping starts it again.
Note: The \" puts quotes around the path, incase it has spaces, which cmd can't process without quotes.
(My code references this answer)
I think starting a new process and closing the existing process is the best way. In this way you have the ability to set some application state for the existing process in between starting and closing processes.
This thread discusses why Application.Restart() may not work in some cases.
System.Diagnostics.Process.Start(Application.ResourceAssembly.Location);
// Set any state that is required to close your current process.
Application.Current.Shutdown();
Or
System.Diagnostics.Process.Start(Application.ExecutablePath);
// Set any state that is required to close your current process.
Application.Exit();

Categories