1414//
1515// Adapted from Rebus
1616
17- using System . Collections . Concurrent ;
18- using System . Runtime . ExceptionServices ;
19-
2017namespace RestSharp ;
2118
2219static class AsyncHelpers {
2320 /// <summary>
24- /// Executes a task synchronously on the calling thread by installing a temporary synchronization context that queues continuations
21+ /// Executes a task on the thread pool, waits for it to complete, and then returns the result.
2522 /// </summary>
26- /// <param name="task">Callback for asynchronous task to run</param>
27- static void RunSync ( Func < Task > task ) {
28- var currentContext = SynchronizationContext . Current ;
29- var customContext = new CustomSynchronizationContext ( task ) ;
30-
31- try {
32- SynchronizationContext . SetSynchronizationContext ( customContext ) ;
33- customContext . Run ( ) ;
34- }
35- finally {
36- SynchronizationContext . SetSynchronizationContext ( currentContext ) ;
37- }
38- }
39-
40- /// <summary>
41- /// Executes a task synchronously on the calling thread by installing a temporary synchronization context that queues continuations
42- /// </summary>
43- /// <param name="task">Callback for asynchronous task to run</param>
23+ /// <param name="func">Callback for asynchronous task to run</param>
4424 /// <typeparam name="T">Return type for the task</typeparam>
4525 /// <returns>Return value from the task</returns>
46- public static T RunSync < T > ( Func < Task < T > > task ) {
47- T result = default ! ;
48- RunSync ( async ( ) => { result = await task ( ) ; } ) ;
49- return result ;
50- }
51-
52- /// <summary>
53- /// Synchronization context that can be "pumped" in order to have it execute continuations posted back to it
54- /// </summary>
55- class CustomSynchronizationContext : SynchronizationContext {
56- readonly ConcurrentQueue < Tuple < SendOrPostCallback , object ? > > _items = new ( ) ;
57- readonly AutoResetEvent _workItemsWaiting = new ( false ) ;
58- readonly Func < Task > _task ;
59- ExceptionDispatchInfo ? _caughtException ;
60- bool _done ;
61-
62- /// <summary>
63- /// Constructor for the custom context
64- /// </summary>
65- /// <param name="task">Task to execute</param>
66- public CustomSynchronizationContext ( Func < Task > task ) =>
67- _task = task ?? throw new ArgumentNullException ( nameof ( task ) , "Please remember to pass a Task to be executed" ) ;
68-
69- /// <summary>
70- /// When overridden in a derived class, dispatches an asynchronous message to a synchronization context.
71- /// </summary>
72- /// <param name="function">Callback function</param>
73- /// <param name="state">Callback state</param>
74- public override void Post ( SendOrPostCallback function , object ? state ) {
75- _items . Enqueue ( Tuple . Create ( function , state ) ) ;
76- _workItemsWaiting . Set ( ) ;
77- }
78-
79- /// <summary>
80- /// Enqueues the function to be executed and executes all resulting continuations until it is completely done
81- /// </summary>
82- public void Run ( ) {
83- Post ( PostCallback , null ) ;
84-
85- while ( ! _done ) {
86- if ( _items . TryDequeue ( out var task ) ) {
87- task . Item1 ( task . Item2 ) ;
88- if ( _caughtException == null ) {
89- continue ;
90- }
91- _caughtException . Throw ( ) ;
92- }
93- else {
94- _workItemsWaiting . WaitOne ( ) ;
95- }
96- }
97-
98- return ;
99-
100- async void PostCallback ( object ? _ ) {
101- try {
102- await _task ( ) . ConfigureAwait ( false ) ;
103- }
104- catch ( Exception exception ) {
105- _caughtException = ExceptionDispatchInfo . Capture ( exception ) ;
106- throw ;
107- }
108- finally {
109- Post ( _ => _done = true , null ) ;
110- }
111- }
112- }
113-
114- /// <summary>
115- /// When overridden in a derived class, dispatches a synchronous message to a synchronization context.
116- /// </summary>
117- /// <param name="function">Callback function</param>
118- /// <param name="state">Callback state</param>
119- public override void Send ( SendOrPostCallback function , object ? state ) => throw new NotSupportedException ( "Cannot send to same thread" ) ;
120-
121- /// <summary>
122- /// When overridden in a derived class, creates a copy of the synchronization context. Not needed, so just return ourselves.
123- /// </summary>
124- /// <returns>Copy of the context</returns>
125- public override SynchronizationContext CreateCopy ( ) => this ;
26+ /// <remarks>
27+ /// Every RunSync call requires at least one thread pool thread. If multiple threads call
28+ /// RunSync simultaneously, then there is a possibility the threadpool could be starved of threads.
29+ /// </remarks>
30+ public static T RunSync < T > ( Func < Task < T > > func ) {
31+ return Task . Run ( func ) . GetAwaiter ( ) . GetResult ( ) ;
12632 }
12733}
0 commit comments