From 64659c0044109c0713bf718065ce96afe4d7ead2 Mon Sep 17 00:00:00 2001 From: David Ellingsworth Date: Wed, 18 Mar 2026 10:35:48 -0400 Subject: [PATCH 1/3] Do not call ConfigureAwait(false) from within the CustomSynchronizationContext. --- src/RestSharp/AsyncHelpers.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/RestSharp/AsyncHelpers.cs b/src/RestSharp/AsyncHelpers.cs index 5d3db9db4..24ec1ded3 100644 --- a/src/RestSharp/AsyncHelpers.cs +++ b/src/RestSharp/AsyncHelpers.cs @@ -97,9 +97,12 @@ public void Run() { return; + // This method is only called from within this custom context for the initial task. 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 thread pool. + await _task(); } catch (Exception exception) { _caughtException = ExceptionDispatchInfo.Capture(exception); From 511ce62eb782ff91bc5d9d7dee5fd99b8a9004ba Mon Sep 17 00:00:00 2001 From: David Ellingsworth Date: Wed, 18 Mar 2026 10:39:39 -0400 Subject: [PATCH 2/3] Internalize the context handling into the Run method of the CustomSynchronizationContext. This simplifies the RunSync method and clarifies the logic of the CustomSynchronizationContext. --- src/RestSharp/AsyncHelpers.cs | 39 +++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/RestSharp/AsyncHelpers.cs b/src/RestSharp/AsyncHelpers.cs index 24ec1ded3..cf86a8ee9 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,20 +73,30 @@ 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; From 39e089f33966e1ebc17d54624dfe5ea38498d2ca Mon Sep 17 00:00:00 2001 From: David Ellingsworth Date: Wed, 18 Mar 2026 13:34:35 -0400 Subject: [PATCH 3/3] The CustomSyncrhonizationContext is designed to run a single task. As such, the Run method should never be called more than once. Make the constructor and Run method private and add a public static Run method to run a single task on an instance of the context. --- src/RestSharp/AsyncHelpers.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/RestSharp/AsyncHelpers.cs b/src/RestSharp/AsyncHelpers.cs index cf86a8ee9..c520d0fa8 100644 --- a/src/RestSharp/AsyncHelpers.cs +++ b/src/RestSharp/AsyncHelpers.cs @@ -25,9 +25,7 @@ static class AsyncHelpers { /// /// Callback for asynchronous task to run static void RunSync(Func task) { - var customContext = new CustomSynchronizationContext(task); - - customContext.Run(); + CustomSynchronizationContext.Run(task); } /// @@ -56,7 +54,7 @@ class CustomSynchronizationContext : SynchronizationContext { /// Constructor for the custom context /// /// Task to execute - public CustomSynchronizationContext(Func task) => + private CustomSynchronizationContext(Func task) => _task = task ?? throw new ArgumentNullException(nameof(task), "Please remember to pass a Task to be executed"); /// @@ -72,7 +70,7 @@ 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() { + private void Run() { var currentContext = SynchronizationContext.Current; try { @@ -117,6 +115,12 @@ async void PostCallback(object? _) { } } + public static void Run(Func task) { + var customContext = new CustomSynchronizationContext(task); + + customContext.Run(); + } + /// /// When overridden in a derived class, dispatches a synchronous message to a synchronization context. ///