@@ -219,6 +219,47 @@ internal static HashSet<JoinableTask> GetDependentTasksFromCandidates(IEnumerabl
219219 return results ;
220220 }
221221
222+ /// <summary>
223+ /// Computes dependency graph to clean up all potential unreachable dependents items.
224+ /// </summary>
225+ /// <param name="syncTask">A thread blocking sychornizing task.</param>
226+ internal static void CleanUpPotentialUnreachableDependentItems ( JoinableTask syncTask )
227+ {
228+ Requires . NotNull ( syncTask , nameof ( syncTask ) ) ;
229+
230+ // a set of tasks may form a dependent loop, so it will make the reference count system
231+ // not to work correctly when we try to remove the synchronous task.
232+ // To get rid of those loops, if a task still tracks the synchronous task after reducing
233+ // the reference count, we will calculate the entire reachable tree from the root. That will
234+ // tell us the exactly tasks which need track the synchronous task, and we will clean up the rest.
235+ HashSet < IJoinableTaskDependent > ? unreachableItems = syncTask . PotentialUnreachableDependents ;
236+ if ( unreachableItems is object )
237+ {
238+ var reachableNodes = new HashSet < IJoinableTaskDependent > ( ) ;
239+ var syncTaskItem = ( IJoinableTaskDependent ) syncTask ;
240+
241+ JoinableTaskDependentData . ComputeSelfAndDescendentOrJoinedJobsAndRemainTasks ( syncTaskItem , reachableNodes , unreachableItems ) ;
242+
243+ // force to remove all invalid items
244+ JoinableTaskDependentData . RemoveUnreachableDependentItems ( syncTask , unreachableItems , reachableNodes ) ;
245+ }
246+ }
247+
248+ /// <summary>
249+ /// Force to clean up all unreachable dependent item, so they are not marked to block the syncTask.
250+ /// </summary>
251+ /// <param name="syncTask">The thread blocking task.</param>
252+ /// <param name="unreachableItems">Unreachable dependent items.</param>
253+ /// <param name="reachableItems">All reachable items.</param>
254+ internal static void RemoveUnreachableDependentItems ( JoinableTask syncTask , HashSet < IJoinableTaskDependent > unreachableItems , HashSet < IJoinableTaskDependent > reachableItems )
255+ {
256+ Requires . NotNull ( syncTask , nameof ( syncTask ) ) ;
257+ Requires . NotNull ( unreachableItems , nameof ( unreachableItems ) ) ;
258+ Requires . NotNull ( reachableItems , nameof ( reachableItems ) ) ;
259+
260+ JoinableTaskDependentData . RemoveUnreachableDependentItems ( syncTask , unreachableItems , reachableItems ) ;
261+ }
262+
222263 /// <summary>
223264 /// Preserve data for the JoinableTask dependency tree. It is holded inside either a <see cref="JoinableTask"/> or a <see cref="JoinableTaskCollection"/>.
224265 /// Do not call methods/properties directly anywhere out of <see cref="JoinableTaskDependencyGraph"/>.
@@ -438,6 +479,59 @@ internal static void OnSynchronousTaskEndToBlockWaiting(JoinableTask syncTask)
438479 }
439480 }
440481
482+ /// <summary>
483+ /// Compute all reachable nodes from a synchronous task. Because we use the result to clean up invalid
484+ /// items from the remain task, we will remove valid task from the collection, and stop immediately if nothing is left.
485+ /// </summary>
486+ /// <param name="taskOrCollection">The current joinableTask or collection owns the data.</param>
487+ /// <param name="reachableNodes">All reachable dependency nodes. This is not a completed list, if there is no remain node.</param>
488+ /// <param name="remainNodes">Remain dependency nodes we want to check. After the execution, it will retain non-reachable nodes.</param>
489+ internal static void ComputeSelfAndDescendentOrJoinedJobsAndRemainTasks ( IJoinableTaskDependent taskOrCollection , HashSet < IJoinableTaskDependent > reachableNodes , HashSet < IJoinableTaskDependent > remainNodes )
490+ {
491+ Requires . NotNull ( taskOrCollection , nameof ( taskOrCollection ) ) ;
492+ Requires . NotNull ( remainNodes , nameof ( remainNodes ) ) ;
493+ Requires . NotNull ( reachableNodes , nameof ( reachableNodes ) ) ;
494+ if ( ( taskOrCollection as JoinableTask ) ? . IsFullyCompleted != true )
495+ {
496+ if ( reachableNodes . Add ( taskOrCollection ) )
497+ {
498+ if ( remainNodes . Remove ( taskOrCollection ) && remainNodes . Count == 0 )
499+ {
500+ // no remain task left, quit the loop earlier
501+ return ;
502+ }
503+
504+ WeakKeyDictionary < IJoinableTaskDependent , int > ? dependencies = taskOrCollection . GetJoinableTaskDependentData ( ) . childDependentNodes ;
505+ if ( dependencies is object )
506+ {
507+ foreach ( KeyValuePair < IJoinableTaskDependent , int > item in dependencies )
508+ {
509+ ComputeSelfAndDescendentOrJoinedJobsAndRemainTasks ( item . Key , reachableNodes , remainNodes ) ;
510+ if ( remainNodes . Count == 0 )
511+ {
512+ return ;
513+ }
514+ }
515+ }
516+ }
517+ }
518+ }
519+
520+ /// <summary>
521+ /// Force to clean up all unreachable dependent item, so they are not marked to block the syncTask.
522+ /// </summary>
523+ /// <param name="syncTask">The thread blocking task.</param>
524+ /// <param name="unreachableItems">Unreachable dependent items.</param>
525+ /// <param name="reachableItems">All reachable items.</param>
526+ internal static void RemoveUnreachableDependentItems ( JoinableTask syncTask , HashSet < IJoinableTaskDependent > unreachableItems , HashSet < IJoinableTaskDependent > reachableItems )
527+ {
528+ HashSet < IJoinableTaskDependent > ? remainPlaceHold = null ;
529+ foreach ( IJoinableTaskDependent ? unreachableItem in unreachableItems )
530+ {
531+ JoinableTaskDependentData . RemoveDependingSynchronousTask ( unreachableItem , syncTask , reachableItems , ref remainPlaceHold ) ;
532+ }
533+ }
534+
441535 /// <summary>
442536 /// Gets all dependent nodes registered in the <see cref="childDependentNodes"/>
443537 /// This method is expected to be used with the JTF lock.
@@ -472,15 +566,43 @@ internal bool HasDirectDependency(IJoinableTaskDependent dependency)
472566 /// </summary>
473567 internal bool HasMainThreadSynchronousTaskWaiting ( )
474568 {
569+ var hasDoneCleanUp = false ;
570+
475571 DependentSynchronousTask ? existingTaskTracking = this . dependingSynchronousTaskTracking ;
476572 while ( existingTaskTracking is object )
477573 {
574+ DependentSynchronousTask ? nextTrackingTask = existingTaskTracking . Next ;
478575 if ( ( existingTaskTracking . SynchronousTask . State & JoinableTask . JoinableTaskFlags . SynchronouslyBlockingMainThread ) == JoinableTask . JoinableTaskFlags . SynchronouslyBlockingMainThread )
479576 {
480- return true ;
577+ if ( existingTaskTracking . SynchronousTask . PotentialUnreachableDependents != null )
578+ {
579+ // This might remove the current tracking item from the linked list, so we capture next node first.
580+ CleanUpPotentialUnreachableDependentItems ( existingTaskTracking . SynchronousTask ) ;
581+
582+ // We need check it again after the cleanup work has finished.
583+ hasDoneCleanUp = true ;
584+ }
585+ else
586+ {
587+ return true ;
588+ }
481589 }
482590
483- existingTaskTracking = existingTaskTracking . Next ;
591+ existingTaskTracking = nextTrackingTask ;
592+ }
593+
594+ if ( hasDoneCleanUp )
595+ {
596+ existingTaskTracking = this . dependingSynchronousTaskTracking ;
597+ while ( existingTaskTracking is object )
598+ {
599+ if ( ( existingTaskTracking . SynchronousTask . State & JoinableTask . JoinableTaskFlags . SynchronouslyBlockingMainThread ) == JoinableTask . JoinableTaskFlags . SynchronouslyBlockingMainThread )
600+ {
601+ return true ;
602+ }
603+
604+ existingTaskTracking = existingTaskTracking . Next ;
605+ }
484606 }
485607
486608 return false ;
@@ -712,7 +834,7 @@ private static void RemoveDependingSynchronousTaskFrom(IReadOnlyList<IJoinableTa
712834 Requires . NotNull ( syncTask , nameof ( syncTask ) ) ;
713835
714836 HashSet < IJoinableTaskDependent > ? reachableNodes = null ;
715- HashSet < IJoinableTaskDependent > ? remainNodes = null ;
837+ HashSet < IJoinableTaskDependent > ? remainNodes = syncTask . PotentialUnreachableDependents ;
716838
717839 if ( force )
718840 {
@@ -724,61 +846,27 @@ private static void RemoveDependingSynchronousTaskFrom(IReadOnlyList<IJoinableTa
724846 RemoveDependingSynchronousTask ( task , syncTask , reachableNodes , ref remainNodes ) ;
725847 }
726848
727- if ( ! force && remainNodes is object && remainNodes . Count > 0 )
849+ if ( remainNodes is object && remainNodes . Count > 0 )
728850 {
729- // a set of tasks may form a dependent loop, so it will make the reference count system
730- // not to work correctly when we try to remove the synchronous task.
731- // To get rid of those loops, if a task still tracks the synchronous task after reducing
732- // the reference count, we will calculate the entire reachable tree from the root. That will
733- // tell us the exactly tasks which need track the synchronous task, and we will clean up the rest.
734- reachableNodes = new HashSet < IJoinableTaskDependent > ( ) ;
735- var syncTaskItem = ( IJoinableTaskDependent ) syncTask ;
736- ComputeSelfAndDescendentOrJoinedJobsAndRemainTasks ( syncTaskItem , reachableNodes , remainNodes ) ;
851+ if ( force )
852+ {
853+ Assumes . True ( reachableNodes ! . Count == 0 ) ;
854+ RemoveUnreachableDependentItems ( syncTask , remainNodes , reachableNodes ) ;
737855
738- // force to remove all invalid items
739- HashSet < IJoinableTaskDependent > ? remainPlaceHold = null ;
740- foreach ( IJoinableTaskDependent ? remainTask in remainNodes )
856+ syncTask . PotentialUnreachableDependents = null ;
857+ }
858+ else if ( syncTask . PotentialUnreachableDependents != remainNodes )
741859 {
742- RemoveDependingSynchronousTask ( remainTask , syncTask , reachableNodes , ref remainPlaceHold ) ;
860+ // a set of tasks may form a dependent loop, so it will make the reference count system
861+ // not to work correctly when we try to remove the synchronous task.
862+ // It will require full dependency scanning to clean them up, which is quite expensive,
863+ // so we keep tracking them, and clean them up when it becomes essential.
864+ syncTask . PotentialUnreachableDependents = remainNodes ;
743865 }
744866 }
745- }
746-
747- /// <summary>
748- /// Compute all reachable nodes from a synchronous task. Because we use the result to clean up invalid
749- /// items from the remain task, we will remove valid task from the collection, and stop immediately if nothing is left.
750- /// </summary>
751- /// <param name="taskOrCollection">The current joinableTask or collection owns the data.</param>
752- /// <param name="reachableNodes">All reachable dependency nodes. This is not a completed list, if there is no remain node.</param>
753- /// <param name="remainNodes">Remain dependency nodes we want to check. After the execution, it will retain non-reachable nodes.</param>
754- private static void ComputeSelfAndDescendentOrJoinedJobsAndRemainTasks ( IJoinableTaskDependent taskOrCollection , HashSet < IJoinableTaskDependent > reachableNodes , HashSet < IJoinableTaskDependent > remainNodes )
755- {
756- Requires . NotNull ( taskOrCollection , nameof ( taskOrCollection ) ) ;
757- Requires . NotNull ( remainNodes , nameof ( remainNodes ) ) ;
758- Requires . NotNull ( reachableNodes , nameof ( reachableNodes ) ) ;
759- if ( ( taskOrCollection as JoinableTask ) ? . IsFullyCompleted != true )
867+ else
760868 {
761- if ( reachableNodes . Add ( taskOrCollection ) )
762- {
763- if ( remainNodes . Remove ( taskOrCollection ) && remainNodes . Count == 0 )
764- {
765- // no remain task left, quit the loop earlier
766- return ;
767- }
768-
769- WeakKeyDictionary < IJoinableTaskDependent , int > ? dependencies = taskOrCollection . GetJoinableTaskDependentData ( ) . childDependentNodes ;
770- if ( dependencies is object )
771- {
772- foreach ( KeyValuePair < IJoinableTaskDependent , int > item in dependencies )
773- {
774- ComputeSelfAndDescendentOrJoinedJobsAndRemainTasks ( item . Key , reachableNodes , remainNodes ) ;
775- if ( remainNodes . Count == 0 )
776- {
777- return ;
778- }
779- }
780- }
781- }
869+ syncTask . PotentialUnreachableDependents = null ;
782870 }
783871 }
784872
0 commit comments