11using JetBrains . Annotations ;
22using System ;
3- using System . Collections . Generic ;
43using System . ComponentModel ;
54using System . Threading ;
65using System . Threading . Tasks ;
@@ -39,8 +38,10 @@ public T ExecuteUntilComplete<T>(ValueTask<T> valueTask)
3938internal sealed class BenchmarkDotNetSynchronizationContext : SynchronizationContext
4039{
4140 private readonly SynchronizationContext ? previousContext ;
42- private readonly Queue < ( SendOrPostCallback d , object ? state ) > queue = new ( ) ;
43- private bool isDisposed ;
41+ // Use 2 arrays to reduce lock contention while executing. The common case is only 1 callback will be queued at a time.
42+ private ( SendOrPostCallback d , object ? state ) [ ] queue = new ( SendOrPostCallback d , object ? state ) [ 1 ] ;
43+ private ( SendOrPostCallback d , object ? state ) [ ] executing = new ( SendOrPostCallback d , object ? state ) [ 1 ] ;
44+ private int queueCount = 0 ;
4445 volatile private bool isCompleted ;
4546
4647 internal BenchmarkDotNetSynchronizationContext ( SynchronizationContext ? previousContext )
@@ -59,28 +60,38 @@ public override void Post(SendOrPostCallback d, object? state)
5960 {
6061 ThrowIfDisposed ( ) ;
6162
62- queue . Enqueue ( ( d , state ) ) ;
63+ int index = queueCount ;
64+ if ( ++ queueCount > queue . Length )
65+ {
66+ Array . Resize ( ref queue , queue . Length * 2 ) ;
67+ }
68+ queue [ index ] = ( d , state ) ;
69+
6370 Monitor . Pulse ( queue ) ;
6471 }
6572 }
6673
67- private void ThrowIfDisposed ( )
68- {
69- if ( isDisposed ) throw new ObjectDisposedException ( nameof ( BenchmarkDotNetSynchronizationContext ) ) ;
70- }
74+ private void ThrowIfDisposed ( ) => _ = queue ?? throw new ObjectDisposedException ( nameof ( BenchmarkDotNetSynchronizationContext ) ) ;
7175
7276 internal void Dispose ( )
7377 {
78+ int count ;
79+ ( SendOrPostCallback d , object ? state ) [ ] executing ;
7480 lock ( queue )
7581 {
7682 ThrowIfDisposed ( ) ;
77- isDisposed = true ;
7883
7984 // Flush any remaining posted callbacks.
80- while ( TryDequeue ( out var callbackAndState ) )
81- {
82- callbackAndState . d ( callbackAndState . state ) ;
83- }
85+ count = queueCount ;
86+ queueCount = 0 ;
87+ executing = queue ;
88+ queue = null ;
89+ }
90+ this . executing = null ;
91+ for ( int i = 0 ; i < count ; ++ i )
92+ {
93+ executing [ i ] . d ( executing [ i ] . state ) ;
94+ executing [ i ] = default ;
8495 }
8596 SetSynchronizationContext ( previousContext ) ;
8697 }
@@ -132,53 +143,46 @@ private void ExecuteUntilComplete()
132143 var spinner = new SpinWait ( ) ;
133144 while ( true )
134145 {
135- if ( TryDequeue ( out var callbackAndState ) )
146+ int count ;
147+ ( SendOrPostCallback d , object ? state ) [ ] executing ;
148+ lock ( queue )
149+ {
150+ count = queueCount ;
151+ queueCount = 0 ;
152+ executing = queue ;
153+ queue = this . executing ;
154+ }
155+ this . executing = executing ;
156+ for ( int i = 0 ; i < count ; ++ i )
157+ {
158+ executing [ i ] . d ( executing [ i ] . state ) ;
159+ executing [ i ] = default ;
160+ }
161+ if ( count > 0 )
136162 {
137- do
138- {
139- callbackAndState . d ( callbackAndState . state ) ;
140- }
141- while ( TryDequeue ( out callbackAndState ) ) ;
142163 // Reset spinner after any posted callback is executed.
143164 spinner = new ( ) ;
165+ continue ;
144166 }
145167
146168 if ( isCompleted )
147169 {
148170 return ;
149171 }
150172
151- if ( spinner . NextSpinWillYield )
173+ if ( ! spinner . NextSpinWillYield )
152174 {
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 ( ) ;
175+ spinner . SpinOnce ( ) ;
160176 continue ;
161177 }
162178
163- spinner . SpinOnce ( ) ;
164- }
165- }
166-
167- private bool TryDequeue ( out ( SendOrPostCallback d , object ? state ) callbackAndState )
168- {
169- lock ( queue )
170- {
171- #if NETSTANDARD2_0
172- if ( queue . Count > 0 )
179+ // Yield the thread and wait for completion or for a posted callback.
180+ lock ( queue )
173181 {
174- callbackAndState = queue . Dequeue ( ) ;
175- return true ;
182+ Monitor . Wait ( queue ) ;
176183 }
177- callbackAndState = default ;
178- return false ;
179- #else
180- return queue . TryDequeue ( out callbackAndState ) ;
181- #endif
184+ // Reset the spinner.
185+ spinner = new ( ) ;
182186 }
183187 }
184188}
0 commit comments