private static void Run(Func <Task> func, LateArrivalBehavior lateArrivalBehavior, Action onFirstLateArrival) { if (func == null) { throw new ArgumentNullException("func"); } var prevCtx = SynchronizationContext.Current; var syncCtx = new SingleThreadSynchronizationContext(); try { syncCtx.Thread = Thread.CurrentThread; syncCtx.onFirstLateArrival = onFirstLateArrival; //Console.WriteLine("Running " + syncCtx.GetHashCode() + " on thread " + Environment.CurrentManagedThreadId); syncCtx.lateArrivalBehavior = lateArrivalBehavior; SynchronizationContext.SetSynchronizationContext(syncCtx); var t = func(); if (t == null) { throw new InvalidOperationException("No task provided."); } t.ContinueWith(delegate { try { var q = syncCtx.m_queue; if (q != null) { q.CompleteAdding(); } } catch { // TODO should we do something here? can it happen? //Console.WriteLine("Perdindirindina"); } syncCtx.completed = true; }, TaskScheduler.Default); syncCtx.RunOnCurrentThread(); if (!syncCtx.aborted) { t.GetAwaiter().GetResult(); } if (syncCtx.aborted) { throw new OperationCanceledException("The SingleThreadSynchronizationContext was aborted."); } } finally { SynchronizationContext.SetSynchronizationContext(prevCtx); var q = syncCtx.m_queue; if (q != null) { q.Dispose(); } syncCtx.m_queue = null; var d = syncCtx.drainingCompletion; if (d != null) { d.TrySetResult(true); } } }
private bool EnqueueJob(Job job, bool allowForward) { if (!completed) { try { var q = m_queue; q.Add(job); return(true); } catch (InvalidOperationException) { } } if (onFirstLateArrival != null) { if (Interlocked.Increment(ref firstLateArrivalCallbackExecuted) == 1) { onFirstLateArrival(); onFirstLateArrival = null; } } if (allowForward && onFailureForwardTo != null) { return(onFailureForwardTo.EnqueueJob(job, false)); } if (lateArrivalBehavior == LateArrivalBehavior.Throw) { #if DESKTOP var frames = GetStackTrace(false).GetFrames(); foreach (var frame in frames) { var m = frame.GetMethod(); if (m != null) { if (m.DeclaringType == typeof(SingleThreadSynchronizationContext)) { continue; } if (m.DeclaringType.GetTypeInfo().Assembly == typeof(Task).GetTypeInfo().Assembly&& m.DeclaringType.Name == "SynchronizationContextAwaitTaskContinuation") { return(false); // We have to pretend nothing happened and not to throw the exception } break; } } #else var frames = new Exception().StackTrace.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); foreach (var frame in frames) { if (frame.Contains("SingleThreadSynchronizationContext")) { continue; } if (frame.Contains("SynchronizationContextAwaitTaskContinuation")) { return(false); } break; } #endif throw new InvalidOperationException("The SingleThreadSynchronizationContext has completed and is no longer accepting continuations or callbacks."); } if (lateArrivalBehavior == LateArrivalBehavior.SpawnNewThread) { lock (this) { if (lateArrivalSyncCtx == null || !lateArrivalSyncCtx.EnqueueJob(job, false)) { var readyTcs = new TaskCompletionSource <bool>(); var firstContinuationExecutedTcs = new TaskCompletionSource <bool>(); Action deleg = () => { SingleThreadSynchronizationContext.Run(async() => { var ctx = (SingleThreadSynchronizationContext)SynchronizationContext.Current; ctx.onFailureForwardTo = this; lateArrivalSyncCtx = ctx; readyTcs.SetResult(true); drainingCompletion.Task.Wait(); await firstContinuationExecutedTcs.Task; await Task.Delay(10000); }, LateArrivalBehavior.Suppress); }; #if DESKTOP new Thread(() => deleg()) { Name = "Respawned thread for SingleThreadSynchronizationContext" }.Start(); #else Task.Run(deleg); #endif readyTcs.Task.Wait(); if (!lateArrivalSyncCtx.EnqueueJob(job, false)) { throw new Exception(); } firstContinuationExecutedTcs.SetResult(true); } return(true); } } return(false); }