@@ -10,12 +10,40 @@ namespace BenchmarkDotNet.Engines;
1010// Used to ensure async continuations are posted back to the same thread that the benchmark process was started on.
1111[ UsedImplicitly ]
1212[ EditorBrowsable ( EditorBrowsableState . Never ) ]
13- public sealed class BenchmarkSynchronizationContext : SynchronizationContext , IDisposable
13+ public readonly ref struct BenchmarkSynchronizationContext : IDisposable
1414{
15- private readonly SynchronizationContext previousContext ;
15+ private readonly BenchmarkDotNetSynchronizationContext context ;
16+
17+ private BenchmarkSynchronizationContext ( BenchmarkDotNetSynchronizationContext context )
18+ {
19+ this . context = context ;
20+ }
21+
22+ public static BenchmarkSynchronizationContext CreateAndSetCurrent ( )
23+ {
24+ var context = new BenchmarkDotNetSynchronizationContext ( SynchronizationContext . Current ) ;
25+ SynchronizationContext . SetSynchronizationContext ( context ) ;
26+ return new ( context ) ;
27+ }
28+
29+ public void Dispose ( )
30+ => context . Dispose ( ) ;
31+
32+ public void ExecuteUntilComplete ( ValueTask valueTask )
33+ => context . ExecuteUntilComplete ( valueTask ) ;
34+
35+ public T ExecuteUntilComplete < T > ( ValueTask < T > valueTask )
36+ => context . ExecuteUntilComplete ( valueTask ) ;
37+ }
38+
39+ internal sealed class BenchmarkDotNetSynchronizationContext : SynchronizationContext
40+ {
41+ private readonly SynchronizationContext ? previousContext ;
1642 private readonly Queue < ( SendOrPostCallback d , object ? state ) > queue = new ( ) ;
43+ private bool isDisposed ;
44+ volatile private bool isCompleted ;
1745
18- private BenchmarkSynchronizationContext ( SynchronizationContext previousContext )
46+ internal BenchmarkDotNetSynchronizationContext ( SynchronizationContext ? previousContext )
1947 {
2048 this . previousContext = previousContext ;
2149 }
@@ -24,53 +52,133 @@ public override SynchronizationContext CreateCopy()
2452 => this ;
2553
2654 public override void Post ( SendOrPostCallback d , object ? state )
27- => queue . Enqueue ( ( d ?? throw new ArgumentNullException ( nameof ( d ) ) , state ) ) ;
55+ {
56+ if ( d is null ) throw new ArgumentNullException ( nameof ( d ) ) ;
2857
29- public static BenchmarkSynchronizationContext CreateAndSetCurrent ( )
58+ lock ( queue )
59+ {
60+ ThrowIfDisposed ( ) ;
61+
62+ queue . Enqueue ( ( d , state ) ) ;
63+ Monitor . Pulse ( queue ) ;
64+ }
65+ }
66+
67+ private void ThrowIfDisposed ( )
3068 {
31- var context = new BenchmarkSynchronizationContext ( Current ) ;
32- SetSynchronizationContext ( context ) ;
33- return context ;
69+ if ( isDisposed ) throw new ObjectDisposedException ( nameof ( BenchmarkDotNetSynchronizationContext ) ) ;
3470 }
3571
36- public void Dispose ( )
37- => SetSynchronizationContext ( previousContext ) ;
72+ internal void Dispose ( )
73+ {
74+ lock ( queue )
75+ {
76+ ThrowIfDisposed ( ) ;
77+ isDisposed = true ;
3878
39- public void ExecuteUntilComplete ( ValueTask valueTask )
79+ // Flush any remaining posted callbacks.
80+ while ( TryDequeue ( out var callbackAndState ) )
81+ {
82+ callbackAndState . d ( callbackAndState . state ) ;
83+ }
84+ }
85+ SetSynchronizationContext ( previousContext ) ;
86+ }
87+
88+ internal void ExecuteUntilComplete ( ValueTask valueTask )
4089 {
41- var spinner = new SpinWait ( ) ;
42- while ( ! valueTask . IsCompleted )
90+ ThrowIfDisposed ( ) ;
91+
92+ var awaiter = valueTask . GetAwaiter ( ) ;
93+ if ( valueTask . IsCompleted )
4394 {
44- DoSpin ( ref spinner ) ;
95+ awaiter . GetResult ( ) ;
96+ return ;
4597 }
46- valueTask . GetAwaiter ( ) . GetResult ( ) ;
98+
99+ isCompleted = false ;
100+ awaiter . UnsafeOnCompleted ( OnCompleted ) ;
101+ ExecuteUntilComplete ( ) ;
102+ awaiter . GetResult ( ) ;
47103 }
48104
49- public T ExecuteUntilComplete < T > ( ValueTask < T > valueTask )
105+ internal T ExecuteUntilComplete < T > ( ValueTask < T > valueTask )
50106 {
51- var spinner = new SpinWait ( ) ;
52- while ( ! valueTask . IsCompleted )
107+ ThrowIfDisposed ( ) ;
108+
109+ var awaiter = valueTask . GetAwaiter ( ) ;
110+ if ( valueTask . IsCompleted )
53111 {
54- DoSpin ( ref spinner ) ;
112+ return awaiter . GetResult ( ) ;
55113 }
56- return valueTask . GetAwaiter ( ) . GetResult ( ) ;
114+
115+ isCompleted = false ;
116+ awaiter . UnsafeOnCompleted ( OnCompleted ) ;
117+ ExecuteUntilComplete ( ) ;
118+ return awaiter . GetResult ( ) ;
57119 }
58120
59- private void DoSpin ( ref SpinWait spinner )
121+ private void OnCompleted ( )
60122 {
61- if ( queue . Count <= 0 )
123+ isCompleted = true ;
124+ lock ( queue )
62125 {
126+ Monitor . Pulse ( queue ) ;
127+ }
128+ }
129+
130+ private void ExecuteUntilComplete ( )
131+ {
132+ var spinner = new SpinWait ( ) ;
133+ while ( true )
134+ {
135+ if ( TryDequeue ( out var callbackAndState ) )
136+ {
137+ do
138+ {
139+ callbackAndState . d ( callbackAndState . state ) ;
140+ }
141+ while ( TryDequeue ( out callbackAndState ) ) ;
142+ // Reset spinner after any posted callback is executed.
143+ spinner = new ( ) ;
144+ }
145+
146+ if ( isCompleted )
147+ {
148+ return ;
149+ }
150+
151+ if ( spinner . NextSpinWillYield )
152+ {
153+ // Yield the thread and wait for completion or for a posted callback.
154+ lock ( queue )
155+ {
156+ Monitor . Wait ( queue ) ;
157+ }
158+ // Reset the spinner.
159+ spinner = new ( ) ;
160+ continue ;
161+ }
162+
63163 spinner . SpinOnce ( ) ;
64- return ;
65164 }
165+ }
66166
67- do
167+ private bool TryDequeue ( out ( SendOrPostCallback d , object ? state ) callbackAndState )
168+ {
169+ lock ( queue )
68170 {
69- var ( d , state ) = queue . Dequeue ( ) ;
70- d ( state ) ;
171+ #if NETSTANDARD2_0
172+ if ( queue . Count > 0 )
173+ {
174+ callbackAndState = queue . Dequeue ( ) ;
175+ return true ;
176+ }
177+ callbackAndState = default ;
178+ return false ;
179+ #else
180+ return queue . TryDequeue ( out callbackAndState ) ;
181+ #endif
71182 }
72- while ( queue . Count > 0 ) ;
73- // Reset spinner after any posted callback is executed.
74- spinner = new ( ) ;
75183 }
76184}
0 commit comments