@@ -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,123 @@ private static async void SomeFireAndForgetMethod()
41264164 await Task . Yield ( ) ;
41274165 }
41284166
4167+ [ MethodImpl ( MethodImplOptions . NoInlining ) ] // We need locals to surely be popped off the stack for a reliable test
4168+ private async Task < JoinableTask > SpinOffMainThreadTaskForJoinableTaskDependenciesHandledAfterTaskCompletion (
4169+ JoinableTaskFactory joinableTaskFactory ,
4170+ JoinableTaskCollection joinableTaskCollection ,
4171+ AsyncManualResetEvent unblockTask2 )
4172+ {
4173+ JoinableTask ? task2 = null ;
4174+ Task ? spinOffTask = null ;
4175+
4176+ JoinableTask ? joinableTask = null ;
4177+
4178+ var mainThreadJoinedCollection = new JoinableTaskCollection ( this . context ) ;
4179+ await TaskScheduler . Default . SwitchTo ( alwaysYield : true ) ;
4180+
4181+ using ( this . context . SuppressRelevance ( ) )
4182+ {
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+ using ( joinableTaskCollection . Join ( ) )
4226+ {
4227+ }
4228+
4229+ using ( mainThreadJoinedCollection . Join ( ) )
4230+ {
4231+ await this . context . Factory . RunAsync ( ( ) =>
4232+ {
4233+ return Task . FromResult ( this . context . IsMainThreadBlocked ( ) ) ;
4234+ } ) ;
4235+
4236+ await spinOffTask ! ;
4237+ }
4238+
4239+ return task2 ;
4240+ }
4241+
4242+ [ MethodImpl ( MethodImplOptions . NoInlining ) ] // We need locals to surely be popped off the stack for a reliable test
4243+ private async Task < ( Task SpinOffTask , WeakReference WeakJoinableTask ) > SpinOffMainThreadTaskForJoinableTaskDependenciesHandledAfterTaskCompletionInnerTask (
4244+ JoinableTaskFactory joinableTaskFactory ,
4245+ JoinableTaskCollection joinableTaskCollection )
4246+ {
4247+ var unblockTask1 = new AsyncManualResetEvent ( ) ;
4248+ var spinOffIsReady = new AsyncManualResetEvent ( ) ;
4249+ var spinOffIsDone = new AsyncManualResetEvent ( ) ;
4250+ Task ? spinOffTask = null ;
4251+
4252+ JoinableTask joinableTask = joinableTaskFactory . RunAsync ( async ( ) =>
4253+ {
4254+ spinOffTask = Task . Run ( async ( ) =>
4255+ {
4256+ JoinableTaskFactory . MainThreadAwaiter awaiter = this . context . Factory . SwitchToMainThreadAsync ( ) . GetAwaiter ( ) ;
4257+ awaiter . OnCompleted ( ( ) =>
4258+ {
4259+ spinOffIsDone . Set ( ) ;
4260+ } ) ;
4261+
4262+ spinOffIsReady . Set ( ) ;
4263+
4264+ await spinOffIsDone . WaitAsync ( ) ;
4265+
4266+ // Add loop dependency
4267+ joinableTaskCollection . Join ( ) ;
4268+ } ) ;
4269+
4270+ await spinOffIsReady . WaitAsync ( ) ;
4271+ await unblockTask1 . WaitAsync ( ) ;
4272+ } ) ;
4273+
4274+ // Increase refcount
4275+ joinableTaskCollection . Add ( joinableTask ) ;
4276+
4277+ unblockTask1 . Set ( ) ;
4278+
4279+ await joinableTask . Task ;
4280+
4281+ return ( spinOffTask ! , new WeakReference ( joinableTask . Task ) ) ;
4282+ }
4283+
41294284 private async Task SomeOperationThatMayBeOnMainThreadAsync ( )
41304285 {
41314286 await Task . Yield ( ) ;
0 commit comments