1111using System . Runtime . CompilerServices ;
1212using QueueType = System . Threading . Channels . Channel < System . Runtime . InteropServices . JavaScript . JSSynchronizationContext . WorkItem > ;
1313
14- namespace System . Runtime . InteropServices . JavaScript {
14+ namespace System . Runtime . InteropServices . JavaScript
15+ {
1516 /// <summary>
1617 /// Provides a thread-safe default SynchronizationContext for the browser that will automatically
1718 /// route callbacks to the main browser thread where they can interact with the DOM and other
1819 /// thread-affinity-having APIs like WebSockets, fetch, WebGL, etc.
1920 /// Callbacks are processed during event loop turns via the runtime's background job system.
2021 /// </summary>
21- internal sealed unsafe class JSSynchronizationContext : SynchronizationContext {
22+ internal sealed unsafe class JSSynchronizationContext : SynchronizationContext
23+ {
2224 public readonly Thread MainThread ;
2325
24- internal readonly struct WorkItem {
26+ internal readonly struct WorkItem
27+ {
2528 public readonly SendOrPostCallback Callback ;
2629 public readonly object ? Data ;
2730 public readonly ManualResetEventSlim ? Signal ;
2831
29- public WorkItem ( SendOrPostCallback callback , object ? data , ManualResetEventSlim ? signal ) {
32+ public WorkItem ( SendOrPostCallback callback , object ? data , ManualResetEventSlim ? signal )
33+ {
3034 Callback = callback ;
3135 Data = data ;
3236 Signal = signal ;
@@ -35,31 +39,33 @@ public WorkItem (SendOrPostCallback callback, object? data, ManualResetEventSlim
3539
3640 private static JSSynchronizationContext ? MainThreadSynchronizationContext ;
3741 private readonly QueueType Queue ;
38- private readonly Action _DataIsAvailable ;
3942
40- private JSSynchronizationContext ( Thread mainThread )
41- : this (
42- mainThread ,
43+ private JSSynchronizationContext ( )
44+ : this (
45+ Thread . CurrentThread ,
4346 Channel . CreateUnbounded < WorkItem > (
4447 new UnboundedChannelOptions { SingleWriter = false , SingleReader = true , AllowSynchronousContinuations = true }
4548 )
4649 )
4750 {
4851 }
4952
50- private JSSynchronizationContext ( Thread mainThread , QueueType queue ) {
53+ private JSSynchronizationContext ( Thread mainThread , QueueType queue )
54+ {
5155 MainThread = mainThread ;
5256 Queue = queue ;
53- _DataIsAvailable = DataIsAvailable ;
5457 }
5558
56- public override SynchronizationContext CreateCopy ( ) {
59+ public override SynchronizationContext CreateCopy ( )
60+ {
5761 return new JSSynchronizationContext ( MainThread , Queue ) ;
5862 }
5963
60- private void AwaitNewData ( ) {
64+ private void AwaitNewData ( )
65+ {
6166 var vt = Queue . Reader . WaitToReadAsync ( ) ;
62- if ( vt . IsCompleted ) {
67+ if ( vt . IsCompleted )
68+ {
6369 DataIsAvailable ( ) ;
6470 return ;
6571 }
@@ -69,31 +75,34 @@ private void AwaitNewData () {
6975 // fire a callback that will schedule a background job to pump the queue on the main thread.
7076 var awaiter = vt . AsTask ( ) . ConfigureAwait ( false ) . GetAwaiter ( ) ;
7177 // UnsafeOnCompleted avoids spending time flowing the execution context (we don't need it.)
72- awaiter . UnsafeOnCompleted ( _DataIsAvailable ) ;
78+ awaiter . UnsafeOnCompleted ( DataIsAvailable ) ;
7379 }
7480
75- private void DataIsAvailable ( ) {
76- // While we COULD pump here, we don't want to. We want the pump to happen on the next event loop turn.
77- // Otherwise we could get a chain where a pump generates a new work item and that makes us pump again, forever.
78- ScheduleBackgroundJob ( ( void * ) ( delegate * unmanaged[ Cdecl] < void > ) & BackgroundJobHandler ) ;
81+ private void DataIsAvailable ( )
82+ {
83+ MainThreadScheduleBackgroundJob ( ( void * ) ( delegate * unmanaged[ Cdecl] < void > ) & BackgroundJobHandler ) ;
7984 }
8085
81- public override void Post ( SendOrPostCallback d , object ? state ) {
86+ public override void Post ( SendOrPostCallback d , object ? state )
87+ {
8288 var workItem = new WorkItem ( d , state , null ) ;
8389 if ( ! Queue . Writer . TryWrite ( workItem ) )
8490 throw new Exception ( "Internal error" ) ;
8591 }
8692
8793 // This path can only run when threading is enabled
88- #pragma warning disable CA1416
94+ #pragma warning disable CA1416
8995
90- public override void Send ( SendOrPostCallback d , object ? state ) {
91- if ( Thread . CurrentThread == MainThread ) {
96+ public override void Send ( SendOrPostCallback d , object ? state )
97+ {
98+ if ( Thread . CurrentThread == MainThread )
99+ {
92100 d ( state ) ;
93101 return ;
94102 }
95103
96- using ( var signal = new ManualResetEventSlim ( false ) ) {
104+ using ( var signal = new ManualResetEventSlim ( false ) )
105+ {
97106 var workItem = new WorkItem ( d , state , signal ) ;
98107 if ( ! Queue . Writer . TryWrite ( workItem ) )
99108 throw new Exception ( "Internal error" ) ;
@@ -102,40 +111,51 @@ public override void Send (SendOrPostCallback d, object? state) {
102111 }
103112 }
104113
105- internal static void Install ( ) {
106- MainThreadSynchronizationContext ??= new JSSynchronizationContext ( Thread . CurrentThread ) ;
107-
108- SynchronizationContext . SetSynchronizationContext ( MainThreadSynchronizationContext ) ;
109- MainThreadSynchronizationContext . AwaitNewData ( ) ;
114+ internal static void Install ( )
115+ {
116+ var ctx = Current as JSSynchronizationContext ;
117+ if ( ctx == null )
118+ {
119+ ctx = new JSSynchronizationContext ( ) ;
120+ MainThreadSynchronizationContext = ctx ;
121+ SetSynchronizationContext ( ctx ) ;
122+ }
123+ ctx . AwaitNewData ( ) ;
110124 }
111125
112126 [ MethodImplAttribute ( MethodImplOptions . InternalCall ) ]
113- internal static extern unsafe void ScheduleBackgroundJob ( void * callback ) ;
127+ internal static extern unsafe void MainThreadScheduleBackgroundJob ( void * callback ) ;
114128
115129#pragma warning disable CS3016 // Arrays as attribute arguments is not CLS-compliant
116130 [ UnmanagedCallersOnly ( CallConvs = new [ ] { typeof ( CallConvCdecl ) } ) ]
117- private static unsafe void BackgroundJobHandler ( ) {
131+ #pragma warning restore CS3016
132+ // this callback will arrive on the bound thread
133+ private static unsafe void BackgroundJobHandler ( )
134+ {
118135 MainThreadSynchronizationContext ! . Pump ( ) ;
119136 }
120137
121- [ UnmanagedCallersOnly ( CallConvs = new [ ] { typeof ( CallConvCdecl ) } ) ]
122- private static unsafe void RequestPumpCallback ( ) {
123- ScheduleBackgroundJob ( ( void * ) ( delegate * unmanaged[ Cdecl] < void > ) & BackgroundJobHandler ) ;
124- }
125-
126- private void Pump ( ) {
127- try {
128- while ( Queue . Reader . TryRead ( out var item ) ) {
129- try {
138+ private void Pump ( )
139+ {
140+ try
141+ {
142+ while ( Queue . Reader . TryRead ( out var item ) )
143+ {
144+ try
145+ {
130146 item . Callback ( item . Data ) ;
131147 // While we would ideally have a catch block here and do something to dispatch/forward unhandled
132148 // exceptions, the standard threadpool (and thus standard synchronizationcontext) have zero
133149 // error handling, so for consistency with them we do nothing. Don't throw in SyncContext callbacks.
134- } finally {
150+ }
151+ finally
152+ {
135153 item . Signal ? . Set ( ) ;
136154 }
137155 }
138- } finally {
156+ }
157+ finally
158+ {
139159 // If an item throws, we want to ensure that the next pump gets scheduled appropriately regardless.
140160 AwaitNewData ( ) ;
141161 }
0 commit comments