@@ -4116,6 +4116,44 @@ public void GetAwaiterCompletedTDoesNotLock()
41164116 lockHolder . Wait ( ) ;
41174117 }
41184118
4119+ [ Fact ]
4120+ public void JoinableTaskCleanUpDependenciesForLoopDependencies ( )
4121+ {
4122+ this . SimulateUIThread ( async delegate
4123+ {
4124+ var joinableTaskCollection = new JoinableTaskCollection ( this . context , refCountAddedJobs : true ) ;
4125+ JoinableTaskFactory taskFactory = this . context . CreateFactory ( joinableTaskCollection ) ;
4126+ ( ( DerivedJoinableTaskFactory ) taskFactory ) . AssumeConcurrentUse = true ;
4127+
4128+ bool isMainThreadBlockedResult = true ;
4129+ var unblockTask2 = new AsyncManualResetEvent ( ) ;
4130+ JoinableTask ? childTask = null ;
4131+
4132+ this . context . Factory . Run ( async ( ) =>
4133+ {
4134+ await TaskScheduler . Default . SwitchTo ( alwaysYield : true ) ;
4135+
4136+ childTask = await this . SpinOffMainThreadTaskForJoinableTaskDependenciesHandledAfterTaskCompletion ( taskFactory , joinableTaskCollection , unblockTask2 ) ;
4137+ } ) ;
4138+
4139+ using ( this . context . SuppressRelevance ( ) )
4140+ {
4141+ isMainThreadBlockedResult = await taskFactory . RunAsync ( ( ) =>
4142+ {
4143+ return Task . FromResult ( this . context . IsMainThreadBlocked ( ) ) ;
4144+ } ) ;
4145+ }
4146+
4147+ using ( this . context . SuppressRelevance ( ) )
4148+ {
4149+ unblockTask2 . Set ( ) ;
4150+ childTask ! . Task . Wait ( ) ;
4151+ }
4152+
4153+ Assert . False ( isMainThreadBlockedResult ) ;
4154+ } ) ;
4155+ }
4156+
41194157 protected override JoinableTaskContext CreateJoinableTaskContext ( )
41204158 {
41214159 return new DerivedJoinableTaskContext ( ) ;
@@ -4126,6 +4164,87 @@ private static async void SomeFireAndForgetMethod()
41264164 await Task . Yield ( ) ;
41274165 }
41284166
4167+ private async Task < JoinableTask > SpinOffMainThreadTaskForJoinableTaskDependenciesHandledAfterTaskCompletion (
4168+ JoinableTaskFactory joinableTaskFactory ,
4169+ JoinableTaskCollection joinableTaskCollection ,
4170+ AsyncManualResetEvent unblockTask2 )
4171+ {
4172+ JoinableTask ? task2 = null ;
4173+ Task ? spinOffTask = null ;
4174+
4175+ JoinableTask ? joinableTask = null ;
4176+
4177+ var mainThreadJoinedCollection = new JoinableTaskCollection ( this . context ) ;
4178+ await TaskScheduler . Default . SwitchTo ( alwaysYield : true ) ;
4179+
4180+ using ( this . context . SuppressRelevance ( ) )
4181+ {
4182+ // this task creates a circular loop.
4183+ task2 = joinableTaskFactory . RunAsync ( async ( ) =>
4184+ {
4185+ joinableTaskCollection . Join ( ) ;
4186+ await unblockTask2 . WaitAsync ( ) ;
4187+ } ) ;
4188+
4189+ var unblockTask1 = new AsyncManualResetEvent ( ) ;
4190+ var spinOffIsReady = new AsyncManualResetEvent ( ) ;
4191+ var spinOffIsDone = new AsyncManualResetEvent ( ) ;
4192+
4193+ joinableTask = joinableTaskFactory . RunAsync ( async ( ) =>
4194+ {
4195+ joinableTaskCollection . Join ( ) ;
4196+
4197+ spinOffTask = Task . Run ( async ( ) =>
4198+ {
4199+ JoinableTaskFactory . MainThreadAwaiter awaiter = this . context . Factory . SwitchToMainThreadAsync ( ) . GetAwaiter ( ) ;
4200+ awaiter . OnCompleted ( ( ) =>
4201+ {
4202+ spinOffIsDone . Set ( ) ;
4203+ } ) ;
4204+
4205+ spinOffIsReady . Set ( ) ;
4206+
4207+ await spinOffIsDone . WaitAsync ( ) ;
4208+ } ) ;
4209+
4210+ await spinOffIsReady . WaitAsync ( ) ;
4211+ await unblockTask1 . WaitAsync ( ) ;
4212+ } ) ;
4213+
4214+ // Increase refcount
4215+ mainThreadJoinedCollection . Add ( joinableTask ) ;
4216+ joinableTaskCollection . Add ( joinableTask ) ;
4217+
4218+ unblockTask1 . Set ( ) ;
4219+
4220+ await joinableTask . Task ;
4221+ }
4222+
4223+ await this . context . Factory . SwitchToMainThreadAsync ( ) ;
4224+
4225+ // Due to circular reference, this add/remove reference to lead incompleted state
4226+ // the joinableTaskCollection will retain refcount to the JTF.Run task.
4227+ using ( joinableTaskCollection . Join ( ) )
4228+ {
4229+ }
4230+
4231+ using ( mainThreadJoinedCollection . Join ( ) )
4232+ {
4233+ // This IsMainThreadBlocked triggers the incompleted state to be cleaned up.
4234+ // JTF.Run joins the completed task through the middle collection to expose the
4235+ // inconsistent between two logic, so the recomputation won't clean up the circular
4236+ // dependency loop correctly.
4237+ await this . context . Factory . RunAsync ( ( ) =>
4238+ {
4239+ return Task . FromResult ( this . context . IsMainThreadBlocked ( ) ) ;
4240+ } ) ;
4241+
4242+ await spinOffTask ! ;
4243+ }
4244+
4245+ return task2 ;
4246+ }
4247+
41294248 private async Task SomeOperationThatMayBeOnMainThreadAsync ( )
41304249 {
41314250 await Task . Yield ( ) ;
0 commit comments