diff --git a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionContext.cs b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionContext.cs index 63f97c300ed6..4e2e7ea8f1cf 100644 --- a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionContext.cs +++ b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionContext.cs @@ -33,7 +33,8 @@ internal class HttpConnectionContext : ConnectionContext, IHttpContextFeature, IHttpTransportFeature, IConnectionInherentKeepAliveFeature, - IConnectionLifetimeFeature + IConnectionLifetimeFeature, + IThreadPoolWorkItem { private static long _tenSeconds = TimeSpan.FromSeconds(10).Ticks; @@ -47,6 +48,10 @@ internal class HttpConnectionContext : ConnectionContext, private IDictionary _items; private CancellationTokenSource _connectionClosedTokenSource; + // No need to RunContinuationsAsynchronously since we're at the tail of a threadpool thread + private readonly TaskCompletionSource _connectionDelegateTcs = new (); + private ConnectionDelegate _connectionDelegate; + private CancellationTokenSource _sendCts; private bool _activeSend; private long _startedSendTime; @@ -547,12 +552,13 @@ private async Task ExecuteApplication(ConnectionDelegate connectionDelegate) // Verify some initialization invariants Debug.Assert(TransportType != HttpTransportType.None, "Transport has not been initialized yet"); - // Jump onto the thread pool thread so blocking user code doesn't block the setup of the - // connection and transport - await AwaitableThreadPool.Yield(); + _connectionDelegate = connectionDelegate; + + // Queue the connection for execution + ThreadPool.UnsafeQueueUserWorkItem(this, preferLocal: false); - // Running this in an async method turns sync exceptions into async ones - await connectionDelegate(this); + // Wait for that task to finish signaling the end of the application execution + await _connectionDelegateTcs.Task; } internal void StartSendCancellation() @@ -589,6 +595,29 @@ internal void StopSendCancellation() } } + public void Execute() + { + async Task ExecuteCore() + { + try + { + await _connectionDelegate(this); + + _connectionDelegateTcs.TrySetResult(); + } + catch (OperationCanceledException ex) + { + _connectionDelegateTcs.TrySetCanceled(ex.CancellationToken); + } + catch (Exception ex) + { + _connectionDelegateTcs.TrySetException(ex); + } + } + + _ = ExecuteCore(); + } + private static class Log { private static readonly Action _disposingConnection =