Skip to content

Commit bff8b9f

Browse files
David EllingsworthDavid Ellingsworth
authored andcommitted
Remove the CustomSynchronizationContext and instead use Task.Run to run async methods safely from the Thread Pool.
1 parent 9b2fe0a commit bff8b9f

1 file changed

Lines changed: 8 additions & 102 deletions

File tree

src/RestSharp/AsyncHelpers.cs

Lines changed: 8 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -14,114 +14,20 @@
1414
//
1515
// Adapted from Rebus
1616

17-
using System.Collections.Concurrent;
18-
using System.Runtime.ExceptionServices;
19-
2017
namespace RestSharp;
2118

2219
static 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

Comments
 (0)