How to pause or resume a process with c#

I wrote this post already, but the plugin I used back then formatted the code badly, so here is the subject again… The code shared today is slightly different to the original post since I have been updating my old code to use newer language features. Other than formatting changes, the main difference here is that in the Suspend and Resume methods, Action<T> delegates in the old code have changed to local functions in the updated code. If you’re still using an old version of the .NET Framework or an older version of the compiler or Visual Studio, you can use the old code. Any new code I share going forward will use the newer language features. (Other newer c# language features I use quite widely, though not in this code, are expression-bodied members, the nameof operator, and auto-implemented properties with initializers.)

Assuming that you created and are controlling another process programmatically, a useful feature of the Process API would be a way to “pause” that process. But there isn’t an API to do so, not for processes anyway. To get around this, my Suspend and Resume extension methods to the Process class iterate and then either suspend or resume all threads of the process. I use this in my code when controlling the ffmpeg console application process, which I use to convert videos.

Firstly, it uses another extension method to convert an ICollection to an array. This:

/// <summary>Converts a non-generic collection to an array.</summary>
public static T[] ToArray<T>(this ICollection collection)
{
    T[] items = new T[collection.Count];
    collection.CopyTo(items, 0);

    return items;
}

And here are my Process extensions:

using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security;
using System.Threading.Tasks;

namespace Romy.Core
{
    /// <summary>Suspend/resume a <see cref="Process"/>
    /// by suspending or resuming all of its threads.</summary>
    /// <remarks><para>
    /// These methods use the <see cref="CollectionExtensions.ToArray&lt;T&gt;"/>
    /// extension method on <b>ICollection</b> to convert a <b>ProcessThreadCollection</b>
    /// to an array.</para>
    /// <para>
    /// The threads are suspended/resumed in parallel for no particular reason, other than the
    /// hope that it may be better to do so as close to all at once as we can.</para></remarks>
    public static class ProcessExtensions
    {
        #region Private Enums

        [Flags]
        private enum ThreadAccess : int
        {
            TERMINATE = (0x0001),
            SUSPEND_RESUME = (0x0002),
            GET_CONTEXT = (0x0008),
            SET_CONTEXT = (0x0010),
            SET_INFORMATION = (0x0020),
            QUERY_INFORMATION = (0x0040),
            SET_THREAD_TOKEN = (0x0080),
            IMPERSONATE = (0x0100),
            DIRECT_IMPERSONATION = (0x0200)
        }

        #endregion Private Enums

        #region Public Methods

        public static void Resume(this Process process)
        {
            void resume(ProcessThread pt)
            {
                IntPtr threadHandle = NativeMethods.OpenThread(
                    ThreadAccess.SUSPEND_RESUME, false, (uint)pt.Id);

                if (threadHandle != IntPtr.Zero)
                {
                    try { NativeMethods.ResumeThread(threadHandle); }
                    finally { NativeMethods.CloseHandle(threadHandle); }
                }
            }

            ProcessThread[] threads = process.Threads.ToArray<ProcessThread>();

            if (threads.Length > 1)
            {
                Parallel.ForEach(threads,
                    new ParallelOptions { MaxDegreeOfParallelism = threads.Length },
                    pt => resume(pt));
            }
            else resume(threads[0]);
        }

        public static void Suspend(this Process process)
        {
            void suspend(ProcessThread pt)
            {
                IntPtr threadHandle = NativeMethods.OpenThread(
                    ThreadAccess.SUSPEND_RESUME, false, (uint)pt.Id);

                if (threadHandle != IntPtr.Zero)
                {
                    try { NativeMethods.SuspendThread(threadHandle); }
                    finally { NativeMethods.CloseHandle(threadHandle); }
                };
            }

            ProcessThread[] threads = process.Threads.ToArray<ProcessThread>();

            if (threads.Length > 1)
            {
                Parallel.ForEach(threads,
                    new ParallelOptions { MaxDegreeOfParallelism = threads.Length },
                    pt => suspend(pt));
            }
            else suspend(threads[0]);
        }

        #endregion Public Methods

        #region Private Classes

        [SuppressUnmanagedCodeSecurity]
        private static class NativeMethods
        {
            #region Public Methods

            [DllImport("kernel32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool CloseHandle(IntPtr hObject);

            [DllImport("kernel32.dll")]
            public static extern IntPtr OpenThread(
                ThreadAccess dwDesiredAccess, bool bInheritHandle, uint dwThreadId);

            [DllImport("kernel32.dll")]
            public static extern uint ResumeThread(IntPtr hThread);

            [DllImport("kernel32.dll")]
            public static extern uint SuspendThread(IntPtr hThread);

            #endregion Public Methods
        }

        #endregion Private Classes
    }
}

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 comment