Nito.AsyncEx.Context/SynchronizationContextSwitcher.cs

using System;
using System.Threading;

namespace Nito.AsyncEx
{
    /// <summary>
    /// Utility class for temporarily switching <see cref="SynchronizationContext"/> implementations.
    /// </summary>
    public sealed class SynchronizationContextSwitcher : IDisposable
    {
        /// <summary>
        /// The previous <see cref="SynchronizationContext"/>.
        /// </summary>
        private readonly SynchronizationContext? _oldContext;

        /// <summary>
        /// Initializes a new instance of the <see cref="SynchronizationContextSwitcher"/> class, installing the new <see cref="SynchronizationContext"/>.
        /// </summary>
        /// <param name="newContext">The new <see cref="SynchronizationContext"/>. This can be <c>null</c> to remove an existing <see cref="SynchronizationContext"/>.</param>
        private SynchronizationContextSwitcher(SynchronizationContext? newContext)
        {
            _oldContext = SynchronizationContext.Current;
            SynchronizationContext.SetSynchronizationContext(newContext);
        }

        /// <summary>
        /// Restores the old <see cref="SynchronizationContext"/>.
        /// </summary>
        public void Dispose()
        {
            SynchronizationContext.SetSynchronizationContext(_oldContext);
        }

        /// <summary>
        /// Executes a synchronous delegate without the current <see cref="SynchronizationContext"/>. The current context is restored when this function returns.
        /// </summary>
        /// <param name="action">The delegate to execute.</param>
        public static void NoContext(Action action)
        {
            _ = action ?? throw new ArgumentNullException(nameof(action));
            using (new SynchronizationContextSwitcher(null))
                action();
        }

        /// <summary>
        /// Executes a synchronous or asynchronous delegate without the current <see cref="SynchronizationContext"/>. The current context is restored when this function synchronously returns.
        /// </summary>
        /// <param name="action">The delegate to execute.</param>
        public static T NoContext<T>(Func<T> action)
        {
            _ = action ?? throw new ArgumentNullException(nameof(action));
            using (new SynchronizationContextSwitcher(null))
                return action();
        }

        /// <summary>
        /// Executes a synchronous delegate with the specified <see cref="SynchronizationContext"/> as "current". The previous current context is restored when this function returns.
        /// </summary>
        /// <param name="context">The context to treat as "current". May be <c>null</c> to indicate the thread pool context.</param>
        /// <param name="action">The delegate to execute.</param>
        public static void ApplyContext(SynchronizationContext context, Action action)
        {
            _ = action ?? throw new ArgumentNullException(nameof(action));
            using (new SynchronizationContextSwitcher(context))
                action();
        }

        /// <summary>
        /// Executes a synchronous or asynchronous delegate without the specified <see cref="SynchronizationContext"/> as "current". The previous current context is restored when this function synchronously returns.
        /// </summary>
        /// <param name="context">The context to treat as "current". May be <c>null</c> to indicate the thread pool context.</param>
        /// <param name="action">The delegate to execute.</param>
        public static T ApplyContext<T>(SynchronizationContext context, Func<T> action)
        {
            _ = action ?? throw new ArgumentNullException(nameof(action));
            using (new SynchronizationContextSwitcher(context))
                return action();
        }
    }
}