From e028625b6362b6a4eb8e324f12037651b91e9c48 Mon Sep 17 00:00:00 2001 From: Lee Oades Date: Mon, 2 Dec 2024 20:45:58 +0000 Subject: [PATCH] Created a custom awaiter that guarantees that we complete on a different threadpool thread. Previously, an awaited Task had a non-zero chance of completing before it was awaited, in which case execution would continue on the same thread. --- .../SynchronizationContextFixture.cs | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/test/Stateless.Tests/SynchronizationContextFixture.cs b/test/Stateless.Tests/SynchronizationContextFixture.cs index c4f8cacd..2a745227 100644 --- a/test/Stateless.Tests/SynchronizationContextFixture.cs +++ b/test/Stateless.Tests/SynchronizationContextFixture.cs @@ -1,4 +1,6 @@ +using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Xunit; @@ -41,15 +43,16 @@ private void CaptureSyncContext() private async Task LoseSyncContext() { - await Task.Run(() => { }).ConfigureAwait(false); // Switch synchronization context and continue + await new CompletesOnDifferentThreadAwaitable(); // Switch synchronization context and continue Assert.NotEqual(_customSynchronizationContext, SynchronizationContext.Current); } /// - /// Tests capture the SynchronizationContext at various points through out their execution. - /// This asserts that every capture is the expected SynchronizationContext instance and that is hasn't been lost. + /// Tests capture the SynchronizationContext at various points throughout their execution. + /// This asserts that every capture is the expected SynchronizationContext instance and that it hasn't been lost. /// /// Ensure that we have the expected number of captures + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local private void AssertSyncContextAlwaysRetained(int numberOfExpectedCalls) { Assert.Equal(numberOfExpectedCalls, _capturedSyncContext.Count); @@ -154,7 +157,7 @@ public async Task Multiple_Deactivations_should_retain_SyncContext() // ASSERT AssertSyncContextAlwaysRetained(3); - } + } [Fact] public async Task Multiple_OnEntry_should_retain_SyncContext() @@ -338,4 +341,21 @@ public async Task InternalTransition_firing_a_sync_action_should_retain_SyncCont // ASSERT AssertSyncContextAlwaysRetained(1); } + + private class CompletesOnDifferentThreadAwaitable + { + public CompletesOnDifferentThreadAwaiter GetAwaiter() => new(); + + internal class CompletesOnDifferentThreadAwaiter : INotifyCompletion + { + public void GetResult() { } + + public bool IsCompleted => false; + + public void OnCompleted(Action continuation) + { + ThreadPool.QueueUserWorkItem(_ => continuation()); + } + } + } } \ No newline at end of file