diff --git a/src/NetEvolve.Extensions.Hosting.WinForms/Internals/WindowsFormsSynchronizationContextProvider.cs b/src/NetEvolve.Extensions.Hosting.WinForms/Internals/WindowsFormsSynchronizationContextProvider.cs
index 1aa8b6f..76a74fe 100644
--- a/src/NetEvolve.Extensions.Hosting.WinForms/Internals/WindowsFormsSynchronizationContextProvider.cs
+++ b/src/NetEvolve.Extensions.Hosting.WinForms/Internals/WindowsFormsSynchronizationContextProvider.cs
@@ -4,18 +4,23 @@
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
-using System.Windows.Forms;
///
+[SuppressMessage(
+ "Usage",
+ "VSTHRD001:Avoid legacy thread switching APIs",
+ Justification = "As designed."
+)]
internal sealed class WindowsFormsSynchronizationContextProvider
: IWindowsFormsSynchronizationContextProvider
{
- internal WindowsFormsSynchronizationContext Context { get; set; } = default!;
+ internal SynchronizationContext Context { get; set; } = default!;
///
public void Invoke([NotNull] Action action)
{
ArgumentNullException.ThrowIfNull(action);
+ ArgumentNullException.ThrowIfNull(Context);
Context.Send(
delegate
@@ -31,6 +36,7 @@ public void Invoke([NotNull] Action action)
public TResult Invoke([NotNull] Func action)
{
ArgumentNullException.ThrowIfNull(action);
+ ArgumentNullException.ThrowIfNull(Context);
TResult result = default!;
Context.Send(
@@ -40,6 +46,7 @@ public TResult Invoke([NotNull] Func action)
},
null
);
+
return result;
}
@@ -48,6 +55,7 @@ public TResult Invoke([NotNull] Func action)
public TResult Invoke([NotNull] Func action, TInput input)
{
ArgumentNullException.ThrowIfNull(action);
+ ArgumentNullException.ThrowIfNull(Context);
TResult result = default!;
Context.Send(
@@ -57,6 +65,7 @@ public TResult Invoke([NotNull] Func action, T
},
null
);
+
return result;
}
@@ -67,6 +76,7 @@ public async ValueTask InvokeAsync(
)
{
ArgumentNullException.ThrowIfNull(action);
+ ArgumentNullException.ThrowIfNull(Context);
var tcs = new TaskCompletionSource();
Context.Post(
@@ -95,6 +105,7 @@ public async ValueTask InvokeAsync(
)
{
ArgumentNullException.ThrowIfNull(action);
+ ArgumentNullException.ThrowIfNull(Context);
var tcs = new TaskCompletionSource();
Context.Post(
@@ -112,6 +123,7 @@ public async ValueTask InvokeAsync(
},
tcs
);
+
return await tcs.Task.WaitAsync(cancellationToken).ConfigureAwait(true);
}
@@ -123,6 +135,7 @@ public async ValueTask InvokeAsync(
)
{
ArgumentNullException.ThrowIfNull(action);
+ ArgumentNullException.ThrowIfNull(Context);
var tcs = new TaskCompletionSource();
Context.Post(
@@ -140,6 +153,7 @@ public async ValueTask InvokeAsync(
},
tcs
);
+
return await tcs.Task.WaitAsync(cancellationToken).ConfigureAwait(true);
}
}
diff --git a/tests/NetEvolve.Extensions.Hosting.WinForms.Tests.Unit/Internals/WindowsFormsSynchronizationContextProviderTests.cs b/tests/NetEvolve.Extensions.Hosting.WinForms.Tests.Unit/Internals/WindowsFormsSynchronizationContextProviderTests.cs
new file mode 100644
index 0000000..650ed72
--- /dev/null
+++ b/tests/NetEvolve.Extensions.Hosting.WinForms.Tests.Unit/Internals/WindowsFormsSynchronizationContextProviderTests.cs
@@ -0,0 +1,365 @@
+namespace NetEvolve.Extensions.Hosting.WinForms.Tests.Unit.Internals;
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+using NetEvolve.Extensions.Hosting.WinForms.Internals;
+using Xunit;
+
+public class WindowsFormsSynchronizationContextProviderTests
+{
+ [Fact]
+ public void Invoke_ActionNull_ThrowsArgumentNullException()
+ {
+ // Arrange
+ var provider = new WindowsFormsSynchronizationContextProvider();
+
+ // Act / Assert
+ _ = Assert.Throws("action", () => provider.Invoke(null!));
+ }
+
+ [Fact]
+ public void Invoke_Action_ContextNull_ThrowsArgumentNullException()
+ {
+ // Arrange
+ var provider = new WindowsFormsSynchronizationContextProvider();
+
+ // Act / Assert
+ _ = Assert.Throws("Context", () => provider.Invoke(() => { }));
+ }
+
+ [Fact]
+ public void Invoke_Action_Expected()
+ {
+ // Arrange
+ // Disable the auto install of the WindowsFormsSynchronizationContext.
+ WindowsFormsSynchronizationContext.AutoInstall = false;
+ var provider = new WindowsFormsSynchronizationContextProvider
+ {
+ Context = SynchronizationContext.Current!
+ };
+
+ // Act / Assert
+ provider.Invoke(() => { });
+
+ Assert.True(true, "No exception was thrown.");
+ }
+
+ [Fact]
+ public void Invoke_FuncNull_ThrowsArgumentNullException()
+ {
+ // Arrange
+ var provider = new WindowsFormsSynchronizationContextProvider();
+
+ // Act / Assert
+ _ = Assert.Throws("action", () => provider.Invoke((Func)null!));
+ }
+
+ [Fact]
+ public void Invoke_Func_ContextNull_ThrowsArgumentNullException()
+ {
+ // Arrange
+ var provider = new WindowsFormsSynchronizationContextProvider();
+
+ // Act / Assert
+ _ = Assert.Throws("Context", () => provider.Invoke(() => 42));
+ }
+
+ [Fact]
+ public void Invoke_Func_Expected()
+ {
+ // Arrange
+ // Disable the auto install of the WindowsFormsSynchronizationContext.
+ WindowsFormsSynchronizationContext.AutoInstall = false;
+ var provider = new WindowsFormsSynchronizationContextProvider
+ {
+ Context = SynchronizationContext.Current!
+ };
+
+ // Act
+ var result = provider.Invoke(() => 42);
+
+ // Assert
+ Assert.Equal(42, result);
+ }
+
+ [Fact]
+ public void Invoke_FuncWithInputNull_ThrowsArgumentNullException()
+ {
+ // Arrange
+ var provider = new WindowsFormsSynchronizationContextProvider();
+
+ // Act / Assert
+ _ = Assert.Throws(
+ "action",
+ () => provider.Invoke((Func)null!, 42)
+ );
+ }
+
+ [Fact]
+ public void Invoke_FuncWithInput_ContextNull_ThrowsArgumentNullException()
+ {
+ // Arrange
+ var provider = new WindowsFormsSynchronizationContextProvider();
+
+ // Act / Assert
+ _ = Assert.Throws(
+ "Context",
+ () => provider.Invoke((int input) => input * 2, 21)
+ );
+ }
+
+ [Fact]
+ public void Invoke_FuncWithInput_Expected()
+ {
+ // Arrange
+ // Disable the auto install of the WindowsFormsSynchronizationContext.
+ WindowsFormsSynchronizationContext.AutoInstall = false;
+ var provider = new WindowsFormsSynchronizationContextProvider
+ {
+ Context = SynchronizationContext.Current!
+ };
+
+ // Act
+ var result = provider.Invoke((int input) => input * 2, 21);
+
+ // Assert
+ Assert.Equal(42, result);
+ }
+
+ [Fact]
+ public async Task InvokeAsync_ActionNull_ThrowsArgumentNullException()
+ {
+ // Arrange
+ var provider = new WindowsFormsSynchronizationContextProvider();
+
+ // Act / Assert
+ _ = await Assert.ThrowsAsync(
+ "action",
+ async () => await provider.InvokeAsync(null!)
+ );
+ }
+
+ [Fact]
+ public async Task InvokeAsync_Action_ContextNull_ThrowsArgumentNullException()
+ {
+ // Arrange
+ var provider = new WindowsFormsSynchronizationContextProvider();
+
+ // Act / Assert
+ _ = await Assert.ThrowsAsync(
+ "Context",
+ async () => await provider.InvokeAsync(() => { })
+ );
+ }
+
+ [Fact]
+ public async Task InvokeAsync_ActionThrows_ExpectedException()
+ { // Arrange
+ // Disable the auto install of the WindowsFormsSynchronizationContext.
+ WindowsFormsSynchronizationContext.AutoInstall = false;
+ var provider = new WindowsFormsSynchronizationContextProvider
+ {
+ Context = SynchronizationContext.Current!
+ };
+
+ // Act
+ _ = await Assert.ThrowsAsync(
+ async () =>
+ await provider.InvokeAsync(() =>
+ {
+ throw new NotImplementedException();
+ })
+ );
+ }
+
+ [Fact]
+ public async Task InvokeAsync_Action_CancellationTokenCanceled_ThrowsTaskCanceledException()
+ {
+ // Arrange
+ // Disable the auto install of the WindowsFormsSynchronizationContext.
+ WindowsFormsSynchronizationContext.AutoInstall = false;
+ var provider = new WindowsFormsSynchronizationContextProvider
+ {
+ Context = SynchronizationContext.Current!
+ };
+
+ // Act
+ _ = await Assert.ThrowsAsync(
+ async () => await provider.InvokeAsync(() => { }, new CancellationToken(true))
+ );
+ }
+
+ [Fact]
+ public async Task InvokeAsync_FuncNull_ThrowsArgumentNullException()
+ {
+ // Arrange
+ var provider = new WindowsFormsSynchronizationContextProvider();
+
+ // Act / Assert
+ _ = await Assert.ThrowsAsync(
+ "action",
+ async () => await provider.InvokeAsync((Func)null!)
+ );
+ }
+
+ [Fact]
+ public async Task InvokeAsync_Func_ContextNull_ThrowsArgumentNullException()
+ {
+ // Arrange
+ var provider = new WindowsFormsSynchronizationContextProvider();
+
+ // Act / Assert
+ _ = await Assert.ThrowsAsync(
+ "Context",
+ async () => await provider.InvokeAsync(() => 42)
+ );
+ }
+
+ [Fact]
+ public async Task InvokeAsync_FuncThrows_ExpectedException()
+ {
+ // Arrange
+ // Disable the auto install of the WindowsFormsSynchronizationContext.
+ WindowsFormsSynchronizationContext.AutoInstall = false;
+ var provider = new WindowsFormsSynchronizationContextProvider
+ {
+ Context = SynchronizationContext.Current!
+ };
+
+ // Act
+ _ = await Assert.ThrowsAsync(
+ async () =>
+ await provider.InvokeAsync(() =>
+ {
+ throw new NotImplementedException();
+ })
+ );
+ }
+
+ [Fact]
+ public async Task InvokeAsync_Func_CancellationTokenCanceled_ThrowsTaskCanceledException()
+ {
+ // Arrange
+ // Disable the auto install of the WindowsFormsSynchronizationContext.
+ WindowsFormsSynchronizationContext.AutoInstall = false;
+ var provider = new WindowsFormsSynchronizationContextProvider
+ {
+ Context = SynchronizationContext.Current!
+ };
+
+ // Act
+ _ = await Assert.ThrowsAsync(
+ async () => await provider.InvokeAsync(() => 42, new CancellationToken(true))
+ );
+ }
+
+ [Fact]
+ public async Task InvokeAsync_Func_Expected()
+ {
+ // Arrange
+ // Disable the auto install of the WindowsFormsSynchronizationContext.
+ WindowsFormsSynchronizationContext.AutoInstall = false;
+ var provider = new WindowsFormsSynchronizationContextProvider
+ {
+ Context = SynchronizationContext.Current!
+ };
+
+ // Act
+ var result = await provider.InvokeAsync(() => 42);
+
+ // Assert
+ Assert.Equal(42, result);
+ }
+
+ [Fact]
+ public async Task InvokeAsync_FuncWithInputNull_ThrowsArgumentNullException()
+ {
+ // Arrange
+ var provider = new WindowsFormsSynchronizationContextProvider();
+
+ // Act / Assert
+ _ = await Assert.ThrowsAsync(
+ "action",
+ async () => await provider.InvokeAsync((Func)null!, 42)
+ );
+ }
+
+ [Fact]
+ public async Task InvokeAsync_FuncWithInput_ContextNull_ThrowsArgumentNullException()
+ {
+ // Arrange
+ var provider = new WindowsFormsSynchronizationContextProvider();
+
+ // Act / Assert
+ _ = await Assert.ThrowsAsync(
+ "Context",
+ async () => await provider.InvokeAsync((int input) => input * 2, 21)
+ );
+ }
+
+ [Fact]
+ public async Task InvokeAsync_FuncWithInputThrows_ExpectedException()
+ {
+ // Arrange
+ // Disable the auto install of the WindowsFormsSynchronizationContext.
+ WindowsFormsSynchronizationContext.AutoInstall = false;
+ var provider = new WindowsFormsSynchronizationContextProvider
+ {
+ Context = SynchronizationContext.Current!
+ };
+
+ // Act
+ _ = await Assert.ThrowsAsync(
+ async () =>
+ await provider.InvokeAsync(
+ (int input) =>
+ {
+ throw new NotImplementedException();
+ },
+ 42
+ )
+ );
+ }
+
+ [Fact]
+ public async Task InvokeAsync_FuncWithInput_CancellationTokenCanceled_ThrowsTaskCanceledException()
+ {
+ // Arrange
+ // Disable the auto install of the WindowsFormsSynchronizationContext.
+ WindowsFormsSynchronizationContext.AutoInstall = false;
+ var provider = new WindowsFormsSynchronizationContextProvider
+ {
+ Context = SynchronizationContext.Current!
+ };
+
+ // Act
+ _ = await Assert.ThrowsAsync(
+ async () =>
+ await provider.InvokeAsync(
+ (int input) => input * 2,
+ 21,
+ new CancellationToken(true)
+ )
+ );
+ }
+
+ [Fact]
+ public async Task InvokeAsync_FuncWithInput_Expected()
+ {
+ // Arrange
+ // Disable the auto install of the WindowsFormsSynchronizationContext.
+ WindowsFormsSynchronizationContext.AutoInstall = false;
+ var provider = new WindowsFormsSynchronizationContextProvider
+ {
+ Context = SynchronizationContext.Current!
+ };
+
+ // Act
+ var result = await provider.InvokeAsync((int input) => input * 2, 21);
+
+ // Assert
+ Assert.Equal(42, result);
+ }
+}