From ec15969d0b2f0d7e2fd73ad6fd9d859582fe6931 Mon Sep 17 00:00:00 2001 From: David Ellingsworth Date: Mon, 16 Mar 2026 16:31:10 -0400 Subject: [PATCH 1/3] Add missing ConfigureAwait on awaited task. --- src/RestSharp/AsyncHelpers.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/RestSharp/AsyncHelpers.cs b/src/RestSharp/AsyncHelpers.cs index 5d3db9db4..01165d3ab 100644 --- a/src/RestSharp/AsyncHelpers.cs +++ b/src/RestSharp/AsyncHelpers.cs @@ -45,7 +45,7 @@ static void RunSync(Func task) { /// Return value from the task public static T RunSync(Func> task) { T result = default!; - RunSync(async () => { result = await task(); }); + RunSync(async () => { result = await task().ConfigureAwait(false); }); return result; } From 66911f916612975bd7e5e814bcd232c318289e10 Mon Sep 17 00:00:00 2001 From: David Ellingsworth Date: Tue, 17 Mar 2026 12:34:23 -0400 Subject: [PATCH 2/3] Refactor the CustomSynchronizationContext to capture the current context when it is run. This simplifies the RunSync method and clarifies the context in which the PostCallback is ran. --- src/RestSharp/AsyncHelpers.cs | 44 ++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/src/RestSharp/AsyncHelpers.cs b/src/RestSharp/AsyncHelpers.cs index 01165d3ab..fd2d9ccb6 100644 --- a/src/RestSharp/AsyncHelpers.cs +++ b/src/RestSharp/AsyncHelpers.cs @@ -25,16 +25,9 @@ static class AsyncHelpers { /// /// Callback for asynchronous task to run static void RunSync(Func task) { - var currentContext = SynchronizationContext.Current; var customContext = new CustomSynchronizationContext(task); - try { - SynchronizationContext.SetSynchronizationContext(customContext); - customContext.Run(); - } - finally { - SynchronizationContext.SetSynchronizationContext(currentContext); - } + customContext.Run(); } /// @@ -80,26 +73,39 @@ public override void Post(SendOrPostCallback function, object? state) { /// Enqueues the function to be executed and executes all resulting continuations until it is completely done /// public void Run() { - Post(PostCallback, null); + var currentContext = SynchronizationContext.Current; + + try { + SynchronizationContext.SetSynchronizationContext(this); + + Post(PostCallback, null); - while (!_done) { - if (_items.TryDequeue(out var task)) { - task.Item1(task.Item2); - if (_caughtException == null) { - continue; + while (!_done) { + if (_items.TryDequeue(out var task)) { + task.Item1(task.Item2); + if (_caughtException == null) { + continue; + } + _caughtException.Throw(); + } + else { + _workItemsWaiting.WaitOne(); } - _caughtException.Throw(); - } - else { - _workItemsWaiting.WaitOne(); } } + finally { + SynchronizationContext.SetSynchronizationContext(currentContext); + } + return; + // This method is only called from within this custom context before the loop above. async void PostCallback(object? _) { try { - await _task().ConfigureAwait(false); + // Do not call ConfigureAwait(false) here to ensure all continuations are + // queued on this context, not the threadpool. + await _task(); } catch (Exception exception) { _caughtException = ExceptionDispatchInfo.Capture(exception); From 7cae0dbd8a28df86304fc9c8c3f5f8c2984b899f Mon Sep 17 00:00:00 2001 From: David Ellingsworth Date: Tue, 17 Mar 2026 14:55:22 -0400 Subject: [PATCH 3/3] Revert "Add missing ConfigureAwait on awaited task." The anonymous method in the RunSync call does not capture the current context until it is executed. Because it is called from within the CustomSynchronizationContext, it will always capture the CustomSynchhronizationContext and not the caller's context. This reverts commit ec15969d0b2f0d7e2fd73ad6fd9d859582fe6931. --- src/RestSharp/AsyncHelpers.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/RestSharp/AsyncHelpers.cs b/src/RestSharp/AsyncHelpers.cs index fd2d9ccb6..bbd0966fe 100644 --- a/src/RestSharp/AsyncHelpers.cs +++ b/src/RestSharp/AsyncHelpers.cs @@ -38,7 +38,7 @@ static void RunSync(Func task) { /// Return value from the task public static T RunSync(Func> task) { T result = default!; - RunSync(async () => { result = await task().ConfigureAwait(false); }); + RunSync(async () => { result = await task(); }); return result; }