38

I am new to the thread model in .NET. What would you use to:

  1. Start a process that handles a file (process.StartInfo.FileName = fileName;).
  2. Wait for the user to close the process OR abandon the thread after some time.
  3. If the user closed the process, delete the file.

Starting the process and waiting should be done on a different thread than the main thread, because this operation should not affect the application.

Example:

My application produces an html report. The user can right click somewhere and say "View Report" - now I retrieve the report contents in a temporary file and launch the process that handles html files i.e. the default browser. The problem is that I cannot cleanup, i.e. delete the temp file.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Bogdan Gavril MSFT
  • 20,615
  • 10
  • 53
  • 74
  • Console ap or WinForms or ASP.NET? – Richard Mar 04 '09 at 15:42
  • Console or WinForms. The process I am starting is local to the machine. – Bogdan Gavril MSFT Mar 04 '09 at 15:47
  • I have an example that uses async/await posted up here-- http://www.allampersandall.com/2013/03/net-process-async-await – Micah Mar 04 '13 at 20:06
  • You might be interested in [this post](http://www.codeducky.org/process-handling-net), which explains how to work with a Process using async/await and timeouts. – ChaseMedallion Aug 29 '14 at 23:49
  • 1
    possible duplicate of [Is there any async equivalent of Process.Start?](http://stackoverflow.com/questions/10788982/is-there-any-async-equivalent-of-process-start) – Chris Moschini Oct 01 '14 at 05:07
  • Marked as dupe of a newer question, that has more current, (and so, likely more relevant) answers – Chris Moschini Oct 01 '14 at 05:08
  • Over the years I've created a Class which is handling starting processes asyncronous with cancellation and other options for starting the process. [You could find the Class here](https://stackoverflow.com/a/56091162/9758687) – Coden May 11 '19 at 14:19

6 Answers6

60

"and waiting must be async" - I'm not trying to be funny, but isn't that a contradiction in terms? However, since you are starting a Process, the Exited event may help:

ProcessStartInfo startInfo = null;
Process process = Process.Start(startInfo);
process.EnableRaisingEvents = true;
process.Exited += delegate {/* clean up*/};

If you want to actually wait (timeout etc), then:

if(process.WaitForExit(timeout)) {
    // user exited
} else {
    // timeout (perhaps process.Kill();)
} 

For waiting async, perhaps just use a different thread?

ThreadPool.QueueUserWorkItem(delegate {
    Process process = Process.Start(startInfo);
    if(process.WaitForExit(timeout)) {
        // user exited
    } else {
        // timeout
    }
});
JJS
  • 6,431
  • 1
  • 54
  • 70
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • I have a question. If i have a function A() { ThreadPool.UnsafeQueueUserWorkItem((o) => { .... proc.WaitForExit(); ... } and in the main method I have like this : A(); Console.Write("aa"); for example... the thing is when console.write finish its work and the thread still didnt finish its work.. the page is loading. and its not waiting for the process to termine. what should i do ? Thanks guys – Grace Jul 04 '11 at 08:54
  • @Grace hard to say based just on that... but unless you *wait* for the worker thread, isn't that exactly what you would *expect* to see? – Marc Gravell Jul 04 '11 at 11:06
  • so i should wait for the thread to termine from the main method ? – Grace Jul 04 '11 at 11:09
  • @Grace that depends on what you are doing; if the *only* thing you are doing is waiting for a process to exit, then... why do that on a worker thread? Needs more context, basically. Otherwise I can't give a useful answer. – Marc Gravell Jul 04 '11 at 11:16
  • :my process converts files to .flv and plays them with javascript.. when i call the function without a Thread.Sleep() after it , i cant get the conversion nor the player bcoz the thread didnt have time to finish its work before the page load..butwhen i put a sleep after the function , thats when im getting my player. – Grace Jul 04 '11 at 11:24
  • @Grace you will need some kind of ajax/polling to handle that scenario if you don't want to block the original request; you can't just write to the old request when it has finished – Marc Gravell Jul 04 '11 at 11:25
  • If anyone is curious what `WaitForExit()` does, it simply uses a `WaitHandle` for its timeout – Chris S Feb 03 '12 at 14:33
  • Hi @MarcGravell, a question. I've done the same, when I open a .ini file and then close it, everything is fine and file gets cleaned. But when I open a .jpg file the story becomes different, it looks like the process is not getting exitted at all and thus the file is not being gattered. If it helps, the process is null when I open a .jpg file, by the way, I am using the overload with (string filename) parameter. Any solution? – Mahdi Tahsildari Jul 31 '15 at 16:59
23

Adding an advanced alternative to this old question. If you want to wait for a process to exit without blocking any thread and still support timeouts, try the following:

    public static Task<bool> WaitForExitAsync(this Process process, TimeSpan timeout)
    {
        ManualResetEvent processWaitObject = new ManualResetEvent(false);
        processWaitObject.SafeWaitHandle = new SafeWaitHandle(process.Handle, false);

        TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();

        RegisteredWaitHandle registeredProcessWaitHandle = null;
        registeredProcessWaitHandle = ThreadPool.RegisterWaitForSingleObject(
            processWaitObject,
            delegate(object state, bool timedOut)
            {
                if (!timedOut)
                {
                    registeredProcessWaitHandle.Unregister(null);
                }

                processWaitObject.Dispose();
                tcs.SetResult(!timedOut);
            },
            null /* state */,
            timeout,
            true /* executeOnlyOnce */);

        return tcs.Task;
    }

Again, the advantage to this approach compared to the accepted answer is that you're not blocking any threads, which reduces the overhead of your app.

Chris Gillum
  • 14,526
  • 5
  • 48
  • 61
3

Try the following code.

public void KickOffProcess(string filePath) {
  var proc = Process.Start(filePath);
  ThreadPool.QueueUserWorkItem(new WaitCallBack(WaitForProc), proc);
}

private void WaitForProc(object obj) {
  var proc = (Process)obj;
  proc.WaitForExit();
  // Do the file deletion here
}
JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
3

The .NET 5 introduced the new API Process.WaitForExitAsync, that allows to wait asynchronously for the completion of a process. It offers the same functionality with the existing Process.WaitForExit, with the only difference being that the waiting is asynchronous, so it does not block the calling thread.

Usage example:

private async void button1_Click(object sender, EventArgs e)
{
    string filePath = Path.Combine
    (
        Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
        Guid.NewGuid().ToString() + ".txt"
    );
    File.WriteAllText(filePath, "Hello World!");
    try
    {
        using Process process = new();
        process.StartInfo.FileName = "Notepad.exe";
        process.StartInfo.Arguments = filePath;
        process.Start();
        await process.WaitForExitAsync();
    }
    finally
    {
        File.Delete(filePath);
    }
    MessageBox.Show("Done!");
}

In the above example the UI remains responsive while the user interacts with the opened file. The UI thread would be blocked if the WaitForExit had been used instead.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • 1
    If I would need this on lower versions of .NET, I would probably copy the source in an extension method. https://source.dot.net/#System.Diagnostics.Process/System/Diagnostics/Process.cs,b6a5b00714a61f06 – Bogdan Gavril MSFT Jun 24 '22 at 10:18
  • @BogdanGavrilMSFT in case it's about starting one process only, I would probably prefer the dirty but safe `await Task.Run(() => process.WaitForExit());`. – Theodor Zoulias Jun 24 '22 at 10:28
  • @TheodorZoulias No. That has zero advantage over the blocking call to `WaitForExit()`, with that additional overhead of `await`. That isn't safe. Don't do that. – Marc L. Jun 27 '23 at 20:43
  • @MarcL I am not sure what you are comparing. Are you saying that the asynchronous `Process.WaitForExitAsync` has zero advantage over the blocking `Process.WaitForExit`, and also that it's not safe? – Theodor Zoulias Jun 27 '23 at 21:02
  • I'm saying that `await Task.Run()` is not a good alternative, since it queues the work onto the ThreadPool. See [Stephen Cleary](https://blog.stephencleary.com/2013/10/taskrun-etiquette-and-proper-usage.html) for more details. – Marc L. Jun 29 '23 at 17:58
  • @MarcL what is a better alternative in your opinion, if you want the functionality of `Process.WaitForExitAsync` and you are targeting a NET platform older than .NET 5? The synchronous `Process.WaitForExit` blocks the UI thread, causing the UI to become non-responsive, so it's out of the question. You have to suggest something better. – Theodor Zoulias Jun 29 '23 at 18:19
0

I would probably not use a separate process for opening a file. Instead, I'd probably utilize a background thread (if I thought the operation was going to take a long time and possible block the UI thread).

private delegate void FileOpenDelegate(string filename);

public void OpenFile(string filename)
{
   FileOpenDelegate fileOpenDelegate = OpenFileAsync;
   AsyncCallback callback = AsyncCompleteMethod;
   fileOpenDelegate.BeginInvoke(filename, callback, state);
}

private void OpenFileAsync(string filename)
{
   // file opening code here, and then do whatever with the file
}

Of course, this is not a good working example (it returns nothing) and I haven't shown how the UI gets updated (you have to use BeginInvoke at the UI level because a background thread cannot update the UI thread). But this approach is generally how I go about handling asynchronous operations in .Net.

Chris Holmes
  • 11,444
  • 12
  • 50
  • 64
0

You can use the Exited event in Process class

ProcessStartInfo info = new ProcessStartInfo();

info.FileName = "notepad.exe";
Process process = Process.Start(info);

process.Exited += new EventHandler(process_Exited);
Console.Read();

and in that event you can handle the operations you mentioned

Pablo Retyk
  • 5,690
  • 6
  • 44
  • 59