Chapter5/5.1_DotnetInteraction/StandardModuleSampleAsync/PowerShellSynchronizationContext.cs
using System;
using System.Collections.Concurrent; using System.Collections.Generic; using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; namespace StandardModuleSampleAsync { /// <summary> /// A synchronisation context that runs all calls scheduled on a single thread. /// </summary> public sealed class PowerShellSynchronizationContext : SynchronizationContext, IDisposable { BlockingCollection<KeyValuePair<SendOrPostCallback, object>> workItemQueue = new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>(); PowerShellSynchronizationContext() { } void IDisposable.Dispose() { if (workItemQueue != null) { workItemQueue.Dispose(); workItemQueue = null; } } void Validate() { if (workItemQueue == null) throw new ObjectDisposedException(GetType().Name); } void RunMessage() { Validate(); KeyValuePair<SendOrPostCallback, object> workItem; while (workItemQueue.TryTake(out workItem, Timeout.InfiniteTimeSpan)) { workItem.Key(workItem.Value); if (workItemQueue == null) break; } } void TerminateMessage() { Validate(); workItemQueue.CompleteAdding(); } public override void Post(SendOrPostCallback callback, object callbackState) { if (callback == null) throw new ArgumentNullException(nameof(callback)); Validate(); try { workItemQueue.Add(new KeyValuePair<SendOrPostCallback, object>(callback, callbackState)); } catch (InvalidOperationException ex) { throw new InvalidOperationException("Cannot enqueue callback, a synchronisationcontext's message has already been terminated.", ex); } } public static void RunSynchronized(Func<Task> asyncOperation) { if (asyncOperation == null) throw new ArgumentNullException(nameof(asyncOperation)); SynchronizationContext savedContext = Current; try { using (var synchronizationContext = new PowerShellSynchronizationContext()) { SetSynchronizationContext(synchronizationContext); var rootOperationTask = asyncOperation(); if (rootOperationTask == null) throw new InvalidOperationException("The asynchronous operation delegate cannot return null."); rootOperationTask.ContinueWith(operationTask => synchronizationContext.TerminateMessage(), TaskScheduler.Default); synchronizationContext.RunMessage(); try { rootOperationTask.GetAwaiter().GetResult(); } catch (AggregateException exs) { AggregateException flattenedAggregate = exs.Flatten(); if (flattenedAggregate.InnerExceptions.Count != 1) throw; ExceptionDispatchInfo.Capture(flattenedAggregate.InnerExceptions[0]).Throw(); } } } finally { SetSynchronizationContext(savedContext); } } public static TResult RunSynchronized<TResult>(Func<Task<TResult>> asyncOperation) { if (asyncOperation == null) throw new ArgumentNullException(nameof(asyncOperation)); SynchronizationContext savedContext = Current; try { using (var synchronizationContext = new PowerShellSynchronizationContext()) { SetSynchronizationContext(synchronizationContext); var rootOperationTask = asyncOperation(); if (rootOperationTask == null) throw new InvalidOperationException("The asynchronous operation delegate cannot return null."); rootOperationTask.ContinueWith(operationTask => synchronizationContext.TerminateMessage(), TaskScheduler.Default); synchronizationContext.RunMessage(); try { return rootOperationTask.GetAwaiter().GetResult(); } catch (AggregateException exs) { AggregateException flattenedAggregate = exs.Flatten(); if (flattenedAggregate.InnerExceptions.Count != 1) throw; ExceptionDispatchInfo.Capture(flattenedAggregate.InnerExceptions[0]).Throw(); throw; } } } finally { SetSynchronizationContext(savedContext); } } } } |