internal void RemoveDependency(JoinableTask joinChild) { Requires.NotNull(joinChild, nameof(joinChild)); using (this.Factory.Context.NoMessagePumpSynchronizationContext.Apply()) { lock (this.owner.Context.SyncContextLock) { int refCount; if (this.childOrJoinedJobs != null && this.childOrJoinedJobs.TryGetValue(joinChild, out refCount)) { if (refCount == 1) { this.childOrJoinedJobs.Remove(joinChild); this.RemoveDependingSynchronousTaskFromChild(joinChild); } else { this.childOrJoinedJobs[joinChild] = --refCount; } } } } }
/// <summary>Runs a loop to process all queued work items, returning only when the task is completed.</summary> internal void CompleteOnCurrentThread() { Assumes.NotNull(this.wrappedTask); // "Push" this task onto the TLS field's virtual stack so that on hang reports we know which task to 'blame'. JoinableTask priorCompletingTask = CompletingTask.Value; CompletingTask.Value = this; try { bool onMainThread = false; var additionalFlags = JoinableTaskFlags.CompletingSynchronously; if (this.owner.Context.IsOnMainThread) { additionalFlags |= JoinableTaskFlags.SynchronouslyBlockingMainThread; onMainThread = true; } this.AddStateFlags(additionalFlags); if (!this.IsCompleteRequested) { if (ThreadingEventSource.Instance.IsEnabled()) { ThreadingEventSource.Instance.CompleteOnCurrentThreadStart(this.GetHashCode(), onMainThread); } using (this.Factory.Context.NoMessagePumpSynchronizationContext.Apply()) { lock (this.owner.Context.SyncContextLock) { this.pendingEventCount = 0; // Add the task to the depending tracking list of itself, so it will monitor the event queue. this.pendingEventSource = this.AddDependingSynchronousTask(this, ref this.pendingEventCount)?.WeakSelf; } } if (onMainThread) { this.owner.Context.OnSynchronousJoinableTaskToCompleteOnMainThread(this); } try { // Don't use IsCompleted as the condition because that // includes queues of posted work that don't have to complete for the // JoinableTask to be ready to return from the JTF.Run method. HashSet <JoinableTask> visited = null; while (!this.IsCompleteRequested) { if (this.TryDequeueSelfOrDependencies(onMainThread, ref visited, out SingleExecuteProtector work, out Task tryAgainAfter)) { work.TryExecute(); } else if (tryAgainAfter != null) { ThreadingEventSource.Instance.WaitSynchronouslyStart(); this.owner.WaitSynchronously(tryAgainAfter); ThreadingEventSource.Instance.WaitSynchronouslyStop(); Assumes.True(tryAgainAfter.IsCompleted); } } }
public Task <T> GetValueAsync(CancellationToken cancellationToken) { if (!((this.value != null && this.value.IsCompleted) || this.recursiveFactoryCheck.Value == null)) { // PERF: we check the condition and *then* retrieve the string resource only on failure // because the string retrieval has shown up as significant on ETL traces. Verify.FailOperation(Strings.ValueFactoryReentrancy); } if (this.value == null) { if (Monitor.IsEntered(this.syncObject)) { // PERF: we check the condition and *then* retrieve the string resource only on failure // because the string retrieval has shown up as significant on ETL traces. Verify.FailOperation(Strings.ValueFactoryReentrancy); } InlineResumable resumableAwaiter = null; lock (this.syncObject) { // Note that if multiple threads hit GetValueAsync() before // the valueFactory has completed its synchronous execution, // then only one thread will execute the valueFactory while the // other threads synchronously block till the synchronous portion // has completed. if (this.value == null) { cancellationToken.ThrowIfCancellationRequested(); resumableAwaiter = new InlineResumable(); Func <Task <T> > originalValueFactory = this.valueFactory; this.valueFactory = null; Func <Task <T> > valueFactory = async delegate { try { await resumableAwaiter; return(await originalValueFactory().ConfigureAwaitRunInline()); } finally { this.jobFactory = null; this.joinableTask = null; } }; this.recursiveFactoryCheck.Value = RecursiveCheckSentinel; try { if (this.jobFactory != null) { // Wrapping with RunAsync allows a future caller // to synchronously block the Main thread waiting for the result // without leading to deadlocks. this.joinableTask = this.jobFactory.RunAsync(valueFactory); this.value = this.joinableTask.Task; } else { this.value = valueFactory(); } } finally { this.recursiveFactoryCheck.Value = null; } } } // Allow the original value factory to actually run. resumableAwaiter?.Resume(); } if (!this.value.IsCompleted) { this.joinableTask?.JoinAsync(cancellationToken).Forget(); } return(this.value.WithCancellation(cancellationToken)); }
/// <summary> /// Check whether a task is being tracked in our tracking list. /// </summary> internal static bool IsDependingSynchronousTask(IJoinableTaskDependent taskItem, JoinableTask syncTask) { Requires.NotNull(taskItem, nameof(taskItem)); return(taskItem.GetJoinableTaskDependentData().IsDependingSynchronousTask(syncTask)); }
/// <summary> /// When the current dependent node is a synchronous task, this method is called after the synchronous is completed, and the thread is no longer blocked. /// This removes the current task from the dependingSynchronousTaskTracking list of the task itself (and propergate through its dependencies.) /// It reverts the data structure change done in the <see cref="OnSynchronousTaskStartToBlockWaiting"/>. /// </summary> internal static void OnSynchronousTaskEndToBlockWaiting(JoinableTask taskItem) { Requires.NotNull(taskItem, nameof(taskItem)); JoinableTaskDependentData.OnSynchronousTaskEndToBlockWaiting(taskItem); }
/// <summary> /// When the current dependent node is a synchronous task, this method is called before the thread is blocked to wait it to complete. /// This adds the current task to the dependingSynchronousTaskTracking list of the task itself (which will propergate through its dependencies.) /// After the task is finished, <see cref="OnSynchronousTaskEndToBlockWaiting"/> is called to revert this change. /// This method is expected to be used with the JTF lock. /// </summary> /// <param name="taskItem">The current joinableTask or collection.</param> /// <param name="taskHasPendingRequests">Return the JoinableTask which has already had pending requests to be handled.</param> /// <param name="pendingRequestsCount">The number of pending requests.</param> internal static void OnSynchronousTaskStartToBlockWaiting(JoinableTask taskItem, out JoinableTask?taskHasPendingRequests, out int pendingRequestsCount) { Requires.NotNull(taskItem, nameof(taskItem)); Assumes.True(Monitor.IsEntered(taskItem.Factory.Context.SyncContextLock)); JoinableTaskDependentData.OnSynchronousTaskStartToBlockWaiting(taskItem, out taskHasPendingRequests, out pendingRequestsCount); }
/// <summary> /// Initializes a new instance of the <see cref="JoinableTaskSynchronizationContext"/> class. /// </summary> /// <param name="joinableTask">The <see cref="JoinableTask"/> that owns this instance.</param> /// <param name="mainThreadAffinitized">A value indicating whether messages posted to this instance should execute on the main thread.</param> internal JoinableTaskSynchronizationContext(JoinableTask joinableTask, bool mainThreadAffinitized) : this(Requires.NotNull(joinableTask, "joinableTask").Factory) { this.job = joinableTask; this.mainThreadAffinitized = mainThreadAffinitized; }
internal ExecutionQueue(JoinableTask owningJob) { Requires.NotNull(owningJob, nameof(owningJob)); this.owningJob = owningJob; }
public DependentSynchronousTask(JoinableTask task) { this.SynchronousTask = task; this.ReferenceCount = 1; }
/// <summary> /// Remove a synchronous task from the tracking list of this task. /// </summary> /// <param name="task">The synchronous task need be removed</param> /// <param name="reachableTasks"> /// If it is not null, it will contain all task which can track the synchronous task. We will ignore reference count in that case. /// </param> /// <param name="remainingDependentTasks">This will retain the tasks which still tracks the synchronous task.</param> private void RemoveDependingSynchronousTask(JoinableTask task, HashSet <JoinableTask> reachableTasks, ref HashSet <JoinableTask> remainingDependentTasks) { Requires.NotNull(task, nameof(task)); DependentSynchronousTask previousTaskTracking = null; DependentSynchronousTask currentTaskTracking = this.dependingSynchronousTaskTracking; bool removed = false; while (currentTaskTracking != null) { if (currentTaskTracking.SynchronousTask == task) { if (--currentTaskTracking.ReferenceCount > 0) { if (reachableTasks != null) { if (!reachableTasks.Contains(this)) { currentTaskTracking.ReferenceCount = 0; } } } if (currentTaskTracking.ReferenceCount == 0) { removed = true; if (previousTaskTracking != null) { previousTaskTracking.Next = currentTaskTracking.Next; } else { this.dependingSynchronousTaskTracking = currentTaskTracking.Next; } } if (reachableTasks == null) { if (removed) { if (remainingDependentTasks != null) { remainingDependentTasks.Remove(this); } } else { if (remainingDependentTasks == null) { remainingDependentTasks = new HashSet <JoinableTask>(); } remainingDependentTasks.Add(this); } } break; } previousTaskTracking = currentTaskTracking; currentTaskTracking = currentTaskTracking.Next; } if (removed && this.childOrJoinedJobs != null) { foreach (var item in this.childOrJoinedJobs) { item.Key.RemoveDependingSynchronousTask(task, reachableTasks, ref remainingDependentTasks); } } }
/// <summary> /// Remove a synchronous task from the tracking list of a list of tasks. /// </summary> /// <param name="tasks">A list of tasks we need update the tracking list.</param> /// <param name="syncTask">The synchronous task we want to remove</param> /// <param name="force">We always remove it from the tracking list if it is true. Otherwise, we keep tracking the reference count.</param> private static void RemoveDependingSynchronousTaskFrom(IReadOnlyList <JoinableTask> tasks, JoinableTask syncTask, bool force) { Requires.NotNull(tasks, nameof(tasks)); Requires.NotNull(syncTask, nameof(syncTask)); HashSet <JoinableTask> reachableTasks = null; HashSet <JoinableTask> remainTasks = null; if (force) { reachableTasks = new HashSet <JoinableTask>(); } foreach (var task in tasks) { task.RemoveDependingSynchronousTask(syncTask, reachableTasks, ref remainTasks); } if (!force && remainTasks != null && remainTasks.Count > 0) { // a set of tasks may form a dependent loop, so it will make the reference count system // not to work correctly when we try to remove the synchronous task. // To get rid of those loops, if a task still tracks the synchronous task after reducing // the reference count, we will calculate the entire reachable tree from the root. That will // tell us the exactly tasks which need track the synchronous task, and we will clean up the rest. reachableTasks = new HashSet <JoinableTask>(); syncTask.ComputeSelfAndDescendentOrJoinedJobsAndRemainTasks(reachableTasks, remainTasks); // force to remove all invalid items HashSet <JoinableTask> remainPlaceHold = null; foreach (var remainTask in remainTasks) { remainTask.RemoveDependingSynchronousTask(syncTask, reachableTasks, ref remainPlaceHold); } } }
/// <summary> /// Tracks a new synchronous task for this task. /// A synchronous task is a task blocking a thread and waits it to be completed. We may want the blocking thread /// to process events from this task. /// </summary> /// <param name="task">The synchronous task</param> /// <param name="totalEventsPending">The total events need be processed</param> /// <returns>The task causes us to trigger the event of the synchronous task, so it can process new events. Null means we don't need trigger any event</returns> private JoinableTask AddDependingSynchronousTask(JoinableTask task, ref int totalEventsPending) { Requires.NotNull(task, nameof(task)); Assumes.True(Monitor.IsEntered(this.owner.Context.SyncContextLock)); if (this.IsCompleted) { return(null); } if (this.IsCompleteRequested) { // A completed task might still have pending items in the queue. int pendingCount = this.GetPendingEventCountForTask(task); if (pendingCount > 0) { totalEventsPending += pendingCount; return(this); } return(null); } DependentSynchronousTask existingTaskTracking = this.dependingSynchronousTaskTracking; while (existingTaskTracking != null) { if (existingTaskTracking.SynchronousTask == task) { existingTaskTracking.ReferenceCount++; return(null); } existingTaskTracking = existingTaskTracking.Next; } int pendingItemCount = this.GetPendingEventCountForTask(task); JoinableTask eventTriggeringTask = null; if (pendingItemCount > 0) { totalEventsPending += pendingItemCount; eventTriggeringTask = this; } // For a new synchronous task, we need apply it to our child tasks. DependentSynchronousTask newTaskTracking = new DependentSynchronousTask(task) { Next = this.dependingSynchronousTaskTracking, }; this.dependingSynchronousTaskTracking = newTaskTracking; if (this.childOrJoinedJobs != null) { foreach (var item in this.childOrJoinedJobs) { var childTiggeringTask = item.Key.AddDependingSynchronousTask(task, ref totalEventsPending); if (eventTriggeringTask == null) { eventTriggeringTask = childTiggeringTask; } } } return(eventTriggeringTask); }
/// <summary>Runs a loop to process all queued work items, returning only when the task is completed.</summary> internal void CompleteOnCurrentThread() { Assumes.NotNull(this.wrappedTask); // "Push" this task onto the TLS field's virtual stack so that on hang reports we know which task to 'blame'. JoinableTask priorCompletingTask = CompletingTask.Value; CompletingTask.Value = this; try { bool onMainThread = false; var additionalFlags = JoinableTaskFlags.CompletingSynchronously; if (this.owner.Context.IsOnMainThread) { additionalFlags |= JoinableTaskFlags.SynchronouslyBlockingMainThread; onMainThread = true; } this.AddStateFlags(additionalFlags); if (!this.IsCompleteRequested) { if (ThreadingEventSource.Instance.IsEnabled()) { ThreadingEventSource.Instance.CompleteOnCurrentThreadStart(this.GetHashCode(), onMainThread); } using (this.Factory.Context.NoMessagePumpSynchronizationContext.Apply()) { lock (this.owner.Context.SyncContextLock) { this.pendingEventCount = 0; // Add the task to the depending tracking list of itself, so it will monitor the event queue. this.pendingEventSource = new WeakReference <JoinableTask>(this.AddDependingSynchronousTask(this, ref this.pendingEventCount)); } } if (onMainThread) { this.owner.Context.OnSynchronousJoinableTaskToCompleteOnMainThread(this); } try { // Don't use IsCompleted as the condition because that // includes queues of posted work that don't have to complete for the // JoinableTask to be ready to return from the JTF.Run method. HashSet <JoinableTask> visited = null; while (!this.IsCompleteRequested) { SingleExecuteProtector work; Task tryAgainAfter; if (this.TryDequeueSelfOrDependencies(onMainThread, ref visited, out work, out tryAgainAfter)) { work.TryExecute(); } else if (tryAgainAfter != null) { ThreadingEventSource.Instance.WaitSynchronouslyStart(); this.owner.WaitSynchronously(tryAgainAfter); ThreadingEventSource.Instance.WaitSynchronouslyStop(); Assumes.True(tryAgainAfter.IsCompleted); } } } finally { using (this.Factory.Context.NoMessagePumpSynchronizationContext.Apply()) { lock (this.owner.Context.SyncContextLock) { // Remove itself from the tracking list, after the task is completed. this.RemoveDependingSynchronousTask(this, true); } } } if (ThreadingEventSource.Instance.IsEnabled()) { ThreadingEventSource.Instance.CompleteOnCurrentThreadStop(this.GetHashCode()); } } else { if (onMainThread) { this.owner.Context.OnSynchronousJoinableTaskToCompleteOnMainThread(this); } } // Now that we're about to stop blocking a thread, transfer any work // that was queued but evidently not required to complete this task // back to the threadpool so it still gets done. if (this.threadPoolQueue?.Count > 0) { SingleExecuteProtector executor; while (this.threadPoolQueue.TryDequeue(out executor)) { ThreadPool.QueueUserWorkItem(SingleExecuteProtector.ExecuteOnceWaitCallback, executor); } } Assumes.True(this.Task.IsCompleted); this.Task.GetAwaiter().GetResult(); // rethrow any exceptions } finally { CompletingTask.Value = priorCompletingTask; } }
/// <summary> /// Raised whenever a joinable task has completed a transition to the main thread. /// </summary> /// <param name="joinableTask">The task whose request to transition to the main thread has completed.</param> /// <param name="canceled">A value indicating whether the transition was cancelled before it was fulfilled.</param> /// <remarks> /// This event is usually raised on the main thread, but can be on another thread when <paramref name="canceled"/> is <c>true</c>. /// </remarks> protected internal override void OnTransitionedToMainThread(JoinableTask joinableTask, bool canceled) { this.innerFactory.OnTransitionedToMainThread(joinableTask, canceled); }
/// <summary> /// Raised when a joinable task has requested a transition to the main thread. /// </summary> /// <param name="joinableTask">The task requesting the transition to the main thread.</param> /// <remarks> /// This event may be raised on any thread, including the main thread. /// </remarks> protected internal override void OnTransitioningToMainThread(JoinableTask joinableTask) { this.innerFactory.OnTransitioningToMainThread(joinableTask); }