@@ -565,4 +565,77 @@ private static void ServiceContract_TypedProxy_AsyncTask_Call_TestImpl(Binding b
565
565
ScenarioTestHelpers . CloseCommunicationObjects ( ( ICommunicationObject ) serviceProxy , factory ) ;
566
566
}
567
567
}
568
+
569
+ [ WcfFact ]
570
+ [ OuterLoop ]
571
+ public static async Task ServiceContract_TypedProxy_Task_Call_AsyncLocal_NonCapture ( )
572
+ {
573
+ // This test verifies a task based call to a service operation doesn't capture any AsyncLocal's in an ExecutionContext
574
+ // This is indirectly checking that the OperationContext won't get captured by a registered CancellationToken callback
575
+ CustomBinding customBinding = null ;
576
+ ChannelFactory < IWcfServiceGenerated > factory = null ;
577
+ EndpointAddress endpointAddress = null ;
578
+ IWcfServiceGenerated serviceProxy = null ;
579
+ string requestString = "Hello" ;
580
+ string result = null ;
581
+ WeakReference < OperationContextExtension > opContextReference = null ;
582
+
583
+ try
584
+ {
585
+ // *** SETUP *** \\
586
+ customBinding = new CustomBinding ( ) ;
587
+ customBinding . Elements . Add ( new TextMessageEncodingBindingElement ( ) ) ;
588
+ customBinding . Elements . Add ( new HttpTransportBindingElement ( ) ) ;
589
+ endpointAddress = new EndpointAddress ( Endpoints . DefaultCustomHttp_Address ) ;
590
+ factory = new ChannelFactory < IWcfServiceGenerated > ( customBinding , endpointAddress ) ;
591
+ serviceProxy = factory . CreateChannel ( ) ;
592
+
593
+ // *** EXECUTE *** \\
594
+ var opExtension = new OperationContextExtension ( ) ;
595
+ opContextReference = new WeakReference < OperationContextExtension > ( opExtension ) ;
596
+ using ( new OperationContextScope ( ( IContextChannel ) serviceProxy ) )
597
+ {
598
+ OperationContext . Current . Extensions . Add ( opExtension ) ;
599
+ result = await serviceProxy . EchoAsync ( requestString ) ;
600
+ }
601
+
602
+ opExtension = null ;
603
+
604
+ // The Task generated by the compiler for this method stores the current ExecutionContext when an await is hit.
605
+ // This means even after we've nulled out the OperationContext via disposing the OperationContextScope, it's still
606
+ // stored in the saved ExecutionContext which was captured by the Task. Forcing another async continuation via
607
+ // Task.Yield() causes the stored ExecutionContext to be replace with the current one which doesn't reference
608
+ // OperationContext any more. Without this, the OperationContext would be kept alive until the next await call,
609
+ // and the weak reference would still hold a reference to the OperationContextExtension.
610
+ await Task . Yield ( ) ;
611
+
612
+ // Force a Gen2 GC so that the WeakReference will no longer have the OperationContextExtension as a target.
613
+ GC . Collect ( 2 ) ;
614
+
615
+ // *** VALIDATE *** \\
616
+ Assert . Equal ( requestString , result ) ;
617
+ Assert . False ( opContextReference . TryGetTarget ( out OperationContextExtension opContext ) , "OperationContextExtension should have been collected" ) ;
618
+ // *** CLEANUP *** \\
619
+ factory . Close ( ) ;
620
+ ( ( ICommunicationObject ) serviceProxy ) . Close ( ) ;
621
+ }
622
+ finally
623
+ {
624
+ // *** ENSURE CLEANUP *** \\
625
+ ScenarioTestHelpers . CloseCommunicationObjects ( ( ICommunicationObject ) serviceProxy , factory ) ;
626
+ }
627
+ }
628
+
629
+ public class OperationContextExtension : IExtension < OperationContext >
630
+ {
631
+ public void Attach ( OperationContext owner )
632
+ {
633
+ // Do nothing
634
+ }
635
+ public void Detach ( OperationContext owner )
636
+ {
637
+ // Do nothing
638
+ }
639
+ }
640
+
568
641
}
0 commit comments