Using "Background Processing Mode" from C#

The Windows Vista kernel added support for I/O and memory priorities. These allow background work (such as search indexing or virus scanning) to reduce its impact on foreground applications beyond what is possible simply by using a low thread CPU priority. According to the SetThreadPriority documentation, “For threads that perform background work such as file I/O, network I/O, or data processing, it is not sufficient to adjust the CPU scheduling priority; even an idle CPU priority thread can easily interfere with system responsiveness when it uses the disk and memory.”

Applications can opt in to low I/O and memory priority by passing new flags to SetThreadPriority (THREAD_MODE_BACKGROUND_BEGIN and THREAD_MODE_BACKGROUND_END) or SetPriorityClass (PROCESS_MODE_BACKGROUND_BEGIN and PROCESS_MODE_BACKGROUND_END).

These new priority levels aren’t exposed through the .NET Framework, but can be accessed by using P/Invoke. First, declare the constants and functions from the Windows API:

internal static class Win32
{
    public const int THREAD_MODE_BACKGROUND_BEGIN = 0x00010000;
    public const int THREAD_MODE_BACKGROUND_END = 0x00020000;
}
 
internal static class NativeMethods
{
    [DllImport("Kernel32.dll", ExactSpelling = true)]
    public static extern IntPtr GetCurrentThread();
 
    [DllImport("Kernel32.dll", ExactSpelling = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool SetThreadPriority(IntPtr hThread, int nPriority);
}

Second, write a C# wrapper for those functions. I use Thread.BeginThreadAffinity to notify the runtime (strictly speaking, the CLR host) that the code that’s being executed depends on the identity of the underlying OS thread. The return type, Scope, has been covered already on this blog.

public static class ThreadUtility
{
    /// <summary>

    /// Puts the current thread into background processing mode.

    /// </summary>

    /// <returns>A Scope that must be disposed to leave background processing mode.</returns>

    [SecurityPermission(SecurityAction.Demand, Flags=SecurityPermissionFlag.ControlThread)]
    public static Scope EnterBackgroundProcessingMode()
    {
        Thread.BeginThreadAffinity();
        IntPtr hThread = SafeNativeMethods.GetCurrentThread();
        if (IsWindowsVista() && NativeMethods.SetThreadPriority(hThread,
            Win32.THREAD_MODE_BACKGROUND_BEGIN))
        {
            // OS supports background processing; return Scope that exits this mode

            return Scope.Create(() =>
            {
                NativeMethods.SetThreadPriority(hThread, Win32.THREAD_MODE_BACKGROUND_END);
                Thread.EndThreadAffinity();
            });
        }
 
        // OS doesn't support background processing mode (or setting it failed)

        Thread.EndThreadAffinity();
        return Scope.Empty;
    }
 
    // Returns true if the current OS is Windows Vista (or Server 2008) or higher.

    private static bool IsWindowsVista()
    {
        OperatingSystem os = Environment.OSVersion;
        return os.Platform == PlatformID.Win32NT && os.Version >= new Version(6, 0);
    }
}

Third, use the wrapper like so:

using (ThreadUtility.EnterBackgroundProcessingMode())
{
    PerformSomeBackgroundWork();
}

Note that this only has effect on Windows Vista, Windows Server 2008, and later; you’d also want to lower Thread.Priority during the background work if your application runs on earlier operating systems.

And while this does appear to work quite nicely in testing, it could actually be dangerous in production code. It’s possible that this could have unpredictable and hazardous interactions with the garbage collector, the finalizer thread, or other components of the .NET Runtime. Furthermore, if a CLR host ever multiplexes many managed threads to one OS thread, changing the priority of the OS thread would be too heavy-handed. Perhaps a future version of the framework will expose background processing mode to managed threads in a safe way; until then it’s probably best to consider advanced native threading features (background mode, fibers, CPU affinity, etc.) to be off limits to managed code.

Posted by Bradley Grainger on October 06, 2008