How to suspend and resume processes in C#

There is no native API, or .Net Framework method, to suspend or resume a process, but it can be done by suspending or resuming the process’ threads. I have written a post about this on my other blog, but this one contains a newer version of my Process extension methods.

The code that follows is part of my pet project that I have written in my spare time the last three years, at the beginning of which I didn’t even know what an extension method, a lambda expression, or LINQ was. The source is shared here: RomyView.zip

First of all, my Process extension methods use the following simple extension method to convert the Process.Threads property, which is a ProcessThreadCollection, to an array. (The return value could also have been declared as an IEnumerable<T>.) This works because ProcessThreadCollection derives from ReadOnlyCollectionbase, which implements ICollection.

  1. /// <summary>Converts a non-generic collection to an array.</summary>
  2. public static T[] ToArray<T>(this ICollection collection)
  3. {
  4.     var items = new T[collection.Count];
  5.     collection.CopyTo(items, 0);
  6.  
  7.     return items;
  8. }

 

Then onto the actual  code to suspend and resume processes. A normal for or foreach loop to iterate the process threads would have worked perfectly also, but I figured it makes more sense to process all the process threads in parallel at the same time, hence I used Parallel.ForEach, specifying ParallelOptions with the MaxDegreeOfParallelism as the number of process threads.

  1. using System;
  2. using System.Diagnostics;
  3. using System.Linq;
  4. using System.Runtime.InteropServices;
  5. using System.Threading.Tasks;
  6.  
  7. namespace Romy.Core
  8. {
  9.     /// <summary>Suspend/resume a <see cref=”System.Diagnostics.Process”/>,
  10.     /// by suspending or resuming all of its threads.</summary>
  11.     /// <remarks><para>
  12.     /// These methods use the <see cref=”CollectionExtensions.ToArray&lt;T&gt;”/> extension method
  13.     /// on <b>ICollection</b> to convert a <b>ProcessThreadCollection</b> to an array.</para>
  14.     /// <para>
  15.     /// The threads are suspended/resumed in parallel for no particular reason, other than the hope
  16.     /// that it may be better to do so as close to all at once as we can.</para></remarks>
  17.     public static class ProcessExtensions
  18.     {
  19.         #region Methods
  20.  
  21.         public static void Suspend(this Process process)
  22.         {
  23.             Action<ProcessThread> suspend = pt =>
  24.             {
  25.                 var threadHandle = NativeMethods.OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)pt.Id);
  26.  
  27.                 if (threadHandle != IntPtr.Zero)
  28.                 {
  29.                     try
  30.                     {
  31.                         NativeMethods.SuspendThread(threadHandle);
  32.                     }
  33.                     finally
  34.                     {
  35.                         NativeMethods.CloseHandle(threadHandle);
  36.                     }
  37.                 };
  38.             };
  39.  
  40.             var threads = process.Threads.ToArray<ProcessThread>();
  41.  
  42.             if (threads.Length > 1)
  43.             {
  44.                 Parallel.ForEach(threads, new ParallelOptions { MaxDegreeOfParallelism = threads.Length }, pt =>
  45.                 {
  46.                     suspend(pt);
  47.                 });
  48.             }
  49.             else
  50.             {
  51.                 suspend(threads[0]);
  52.             }
  53.         }
  54.  
  55.         public static void Resume(this Process process)
  56.         {
  57.             Action<ProcessThread> resume = pt =>
  58.             {
  59.                 var threadHandle = NativeMethods.OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)pt.Id);
  60.  
  61.                 if (threadHandle != IntPtr.Zero)
  62.                 {
  63.                     try
  64.                     {
  65.                         NativeMethods.ResumeThread(threadHandle);
  66.                     }
  67.                     finally
  68.                     {
  69.                         NativeMethods.CloseHandle(threadHandle);
  70.                     }
  71.                 }
  72.             };
  73.  
  74.             var threads = process.Threads.ToArray<ProcessThread>();
  75.  
  76.             if (threads.Length > 1)
  77.             {
  78.                 Parallel.ForEach(threads, new ParallelOptions { MaxDegreeOfParallelism = threads.Length }, pt =>
  79.                 {
  80.                     resume(pt);
  81.                 });
  82.             }
  83.             else
  84.             {
  85.                 resume(threads[0]);
  86.             }
  87.         }
  88.  
  89.         #endregion
  90.  
  91.         #region Interop
  92.  
  93.         static class NativeMethods
  94.         {
  95.             [DllImport(“kernel32.dll”)]
  96.             [return: MarshalAs(UnmanagedType.Bool)]
  97.             public static extern bool CloseHandle(IntPtr hObject);
  98.  
  99.             [DllImport(“kernel32.dll”)]
  100.             public static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, uint dwThreadId);
  101.  
  102.             [DllImport(“kernel32.dll”)]
  103.             public static extern uint SuspendThread(IntPtr hThread);
  104.  
  105.             [DllImport(“kernel32.dll”)]
  106.             public static extern uint ResumeThread(IntPtr hThread);
  107.         }
  108.  
  109.         [Flags]
  110.         enum ThreadAccess : int
  111.         {
  112.             TERMINATE = (0x0001),
  113.             SUSPEND_RESUME = (0x0002),
  114.             GET_CONTEXT = (0x0008),
  115.             SET_CONTEXT = (0x0010),
  116.             SET_INFORMATION = (0x0020),
  117.             QUERY_INFORMATION = (0x0040),
  118.             SET_THREAD_TOKEN = (0x0080),
  119.             IMPERSONATE = (0x0100),
  120.             DIRECT_IMPERSONATION = (0x0200)
  121.         }
  122.  
  123.         #endregion
  124.     }
  125. }

 

Disclaimer: Don’t expect this code to work on anybody’s random process that you obtained via Process.GetProcessesByName. I use this in my application code, while converting videos, to pause and resume ffmpeg.exe processes, where they are processes that I launched programmatically. In other words, they are actually child processes of my application’s process.

Advertisements

About Jerome

I am a senior C# developer in Johannesburg, South Africa. I am also a recovering addict, who spent nearly eight years using methamphetamine. I write on my recovery blog about my lessons learned and sometimes give advice to others who have made similar mistakes, often from my viewpoint as an atheist, and I also write some C# programming articles on my programming blog.
This entry was posted in Programming and tagged , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s