Skip to content

Commit 0ea1cbe

Browse files
symbiogenesisEdward Miller
andauthored
support ValueTask<T> (#203)
* support ValueTask<T> * fix tests * add test --------- Co-authored-by: Edward Miller <[email protected]>
1 parent c0c6339 commit 0ea1cbe

File tree

5 files changed

+102
-6
lines changed

5 files changed

+102
-6
lines changed

src/AsyncAwaitBestPractices.UnitTests/BaseTest.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,31 @@ protected static async Task IntParameterDelayedNullReferenceExceptionTask(int de
4242
throw new NullReferenceException();
4343
}
4444

45+
protected static async ValueTask NoParameterValueTask() => await Task.Delay(Delay);
46+
protected static async ValueTask IntParameterValueTask(int delay) => await Task.Delay(delay);
47+
protected static async ValueTask NullableIntParameterValueTask(int? delay) => await Task.Delay(delay ?? Delay);
48+
protected static async ValueTask StringParameterValueTask(string? text) => await Task.Delay(Delay);
49+
protected static ValueTask NoParameterImmediateNullReferenceExceptionValueTask() => throw new NullReferenceException();
50+
protected static ValueTask ParameterImmediateNullReferenceExceptionValueTask(int delay) => throw new NullReferenceException();
51+
52+
protected static async ValueTask NoParameterDelayedNullReferenceExceptionValueTask()
53+
{
54+
await Task.Delay(Delay);
55+
throw new NullReferenceException();
56+
}
57+
58+
protected static async ValueTask<bool> NoParameterDelayedNullReferenceExceptionValueTaskWithReturn()
59+
{
60+
await Task.Delay(Delay);
61+
throw new NullReferenceException();
62+
}
63+
64+
protected static async ValueTask IntParameterDelayedNullReferenceExceptionValueTask(int delay)
65+
{
66+
await Task.Delay(delay);
67+
throw new NullReferenceException();
68+
}
69+
4570
protected static bool CanExecuteTrue(bool parameter) => true;
4671
protected static bool CanExecuteTrue(int parameter) => true;
4772
protected static bool CanExecuteTrue(string? parameter) => true;

src/AsyncAwaitBestPractices.UnitTests/SafeFireAndForgetTests/Tests_ValueTask_SafeFIreAndForgetT.cs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public async Task SafeFireAndForget_HandledException()
2727
NullReferenceException? exception = null;
2828

2929
//Act
30-
NoParameterDelayedNullReferenceExceptionTask().SafeFireAndForget<NullReferenceException>(ex => exception = ex);
30+
NoParameterDelayedNullReferenceExceptionValueTask().SafeFireAndForget<NullReferenceException>(ex => exception = ex);
3131
await BaseTest.NoParameterTask();
3232
await BaseTest.NoParameterTask();
3333

@@ -43,14 +43,35 @@ public async Task SafeFireAndForgetT_SetDefaultExceptionHandling_NoParams()
4343
SafeFireAndForgetExtensions.SetDefaultExceptionHandling(ex => exception = ex);
4444

4545
//Act
46-
NoParameterDelayedNullReferenceExceptionTask().SafeFireAndForget();
46+
NoParameterDelayedNullReferenceExceptionValueTask().SafeFireAndForget();
4747
await BaseTest.NoParameterTask();
4848
await BaseTest.NoParameterTask();
4949

5050
//Assert
5151
Assert.That(exception, Is.Not.Null);
5252
}
5353

54+
[Test]
55+
public async Task SafeFireAndForgetT_SetDefaultExceptionHandling_WithParam()
56+
{
57+
//Arrange
58+
Exception? exception1 = null;
59+
NullReferenceException? exception2 = null;
60+
SafeFireAndForgetExtensions.SetDefaultExceptionHandling(ex => exception1 = ex);
61+
62+
//Act
63+
NoParameterDelayedNullReferenceExceptionValueTask().SafeFireAndForget<NullReferenceException>(ex => exception2 = ex);
64+
await BaseTest.NoParameterTask();
65+
await BaseTest.NoParameterTask();
66+
67+
Assert.Multiple(() =>
68+
{
69+
//Assert
70+
Assert.That(exception1, Is.Not.Null);
71+
Assert.That(exception2, Is.Not.Null);
72+
});
73+
}
74+
5475
[Test]
5576
public async Task SafeFireAndForgetT_SetDefaultExceptionHandling_WithParams()
5677
{
@@ -60,7 +81,7 @@ public async Task SafeFireAndForgetT_SetDefaultExceptionHandling_WithParams()
6081
SafeFireAndForgetExtensions.SetDefaultExceptionHandling(ex => exception1 = ex);
6182

6283
//Act
63-
NoParameterDelayedNullReferenceExceptionTask().SafeFireAndForget<NullReferenceException>(ex => exception2 = ex);
84+
NoParameterDelayedNullReferenceExceptionValueTaskWithReturn().SafeFireAndForget<bool, NullReferenceException>(ex => exception2 = ex);
6485
await BaseTest.NoParameterTask();
6586
await BaseTest.NoParameterTask();
6687

src/AsyncAwaitBestPractices.UnitTests/SafeFireAndForgetTests/Tests_ValueTask_SafeFireAndForget.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public async Task SafeFireAndForget_HandledException()
2727
Exception? exception = null;
2828

2929
//Act
30-
NoParameterDelayedNullReferenceExceptionTask().SafeFireAndForget(ex => exception = ex);
30+
NoParameterDelayedNullReferenceExceptionValueTask().SafeFireAndForget(ex => exception = ex);
3131
await BaseTest.NoParameterTask();
3232
await BaseTest.NoParameterTask();
3333

@@ -43,7 +43,7 @@ public async Task SafeFireAndForget_SetDefaultExceptionHandling_NoParams()
4343
SafeFireAndForgetExtensions.SetDefaultExceptionHandling(ex => exception = ex);
4444

4545
//Act
46-
NoParameterDelayedNullReferenceExceptionTask().SafeFireAndForget();
46+
NoParameterDelayedNullReferenceExceptionValueTask().SafeFireAndForget();
4747
await BaseTest.NoParameterTask();
4848
await BaseTest.NoParameterTask();
4949

@@ -60,7 +60,7 @@ public async Task SafeFireAndForget_SetDefaultExceptionHandling_WithParams()
6060
SafeFireAndForgetExtensions.SetDefaultExceptionHandling(ex => exception1 = ex);
6161

6262
//Act
63-
NoParameterDelayedNullReferenceExceptionTask().SafeFireAndForget(ex => exception2 = ex);
63+
NoParameterDelayedNullReferenceExceptionValueTask().SafeFireAndForget(ex => exception2 = ex);
6464
await BaseTest.NoParameterTask();
6565
await BaseTest.NoParameterTask();
6666

src/AsyncAwaitBestPractices/SafeFireAndForgetExtensions.extensions.shared.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ public static partial class SafeFireAndForgetExtensions
1616
/// <param name="continueOnCapturedContext">If set to <c>true</c>, continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to <c>false</c>, continue on a different context; this will allow the Synchronization Context to continue on a different thread</param>
1717
public static void SafeFireAndForget(this ValueTask task, Action<Exception>? onException, bool continueOnCapturedContext = false) => task.SafeFireAndForget(in onException, in continueOnCapturedContext);
1818

19+
/// <summary>
20+
/// Safely execute the ValueTask without waiting for it to complete before moving to the next line of code; commonly known as "Fire And Forget". Inspired by John Thiriet's blog post, "Removing Async Void": https://johnthiriet.com/removing-async-void/.
21+
/// </summary>
22+
/// <param name="task">ValueTask.</param>
23+
/// <param name="onException">If an exception is thrown in the ValueTask, <c>onException</c> will execute. If onException is null, the exception will be re-thrown</param>
24+
/// <param name="continueOnCapturedContext">If set to <c>true</c>, continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to <c>false</c>, continue on a different context; this will allow the Synchronization Context to continue on a different thread</param>
25+
/// <typeparam name="T">The return value of the ValueTask.</typeparam>
26+
public static void SafeFireAndForget<T>(this ValueTask<T> task, Action<Exception>? onException, bool continueOnCapturedContext = false) => task.SafeFireAndForget(in onException, in continueOnCapturedContext);
1927

2028
/// <summary>
2129
/// Safely execute the ValueTask without waiting for it to complete before moving to the next line of code; commonly known as "Fire And Forget". Inspired by John Thiriet's blog post, "Removing Async Void": https://johnthiriet.com/removing-async-void/.
@@ -26,6 +34,15 @@ public static partial class SafeFireAndForgetExtensions
2634
/// <typeparam name="TException">Exception type. If an exception is thrown of a different type, it will not be handled</typeparam>
2735
public static void SafeFireAndForget<TException>(this ValueTask task, Action<TException>? onException, bool continueOnCapturedContext = false) where TException : Exception => task.SafeFireAndForget(in onException, in continueOnCapturedContext);
2836

37+
/// <summary>
38+
/// Safely execute the ValueTask without waiting for it to complete before moving to the next line of code; commonly known as "Fire And Forget". Inspired by John Thiriet's blog post, "Removing Async Void": https://johnthiriet.com/removing-async-void/.
39+
/// </summary>
40+
/// <param name="task">ValueTask.</param>
41+
/// <param name="onException">If an exception is thrown in the Task, <c>onException</c> will execute. If onException is null, the exception will be re-thrown</param>
42+
/// <param name="continueOnCapturedContext">If set to <c>true</c>, continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to <c>false</c>, continue on a different context; this will allow the Synchronization Context to continue on a different thread</param>
43+
/// <typeparam name="T">The return value of the ValueTask.</typeparam>
44+
/// <typeparam name="TException">Exception type. If an exception is thrown of a different type, it will not be handled</typeparam>
45+
public static void SafeFireAndForget<T, TException>(this ValueTask<T> task, Action<TException>? onException, bool continueOnCapturedContext = false) where TException : Exception => task.SafeFireAndForget(in onException, in continueOnCapturedContext);
2946

3047
/// <summary>
3148
/// Safely execute the Task without waiting for it to complete before moving to the next line of code; commonly known as "Fire And Forget". Inspired by John Thiriet's blog post, "Removing Async Void": https://johnthiriet.com/removing-async-void/.

src/AsyncAwaitBestPractices/SafeFireAndForgetExtensions.shared.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ public static partial class SafeFireAndForgetExtensions
1919
/// <param name="continueOnCapturedContext">If set to <c>true</c>, continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to <c>false</c>, continue on a different context; this will allow the Synchronization Context to continue on a different thread</param>
2020
public static void SafeFireAndForget(this ValueTask task, in Action<Exception>? onException = null, in bool continueOnCapturedContext = false) => HandleSafeFireAndForget(task, continueOnCapturedContext, onException);
2121

22+
/// <summary>
23+
/// Safely execute the ValueTask without waiting for it to complete before moving to the next line of code; commonly known as "Fire And Forget". Inspired by John Thiriet's blog post, "Removing Async Void": https://johnthiriet.com/removing-async-void/.
24+
/// </summary>
25+
/// <param name="task">ValueTask.</param>
26+
/// <param name="onException">If an exception is thrown in the ValueTask, <c>onException</c> will execute. If onException is null, the exception will be re-thrown</param>
27+
/// <param name="continueOnCapturedContext">If set to <c>true</c>, continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to <c>false</c>, continue on a different context; this will allow the Synchronization Context to continue on a different thread</param>
28+
/// <typeparam name="T">The return value of the ValueTask.</typeparam>
29+
public static void SafeFireAndForget<T>(this ValueTask<T> task, in Action<Exception>? onException = null, in bool continueOnCapturedContext = false) => HandleSafeFireAndForget(task, continueOnCapturedContext, onException);
2230

2331
/// <summary>
2432
/// Safely execute the ValueTask without waiting for it to complete before moving to the next line of code; commonly known as "Fire And Forget". Inspired by John Thiriet's blog post, "Removing Async Void": https://johnthiriet.com/removing-async-void/.
@@ -29,6 +37,16 @@ public static partial class SafeFireAndForgetExtensions
2937
/// <typeparam name="TException">Exception type. If an exception is thrown of a different type, it will not be handled</typeparam>
3038
public static void SafeFireAndForget<TException>(this ValueTask task, in Action<TException>? onException = null, in bool continueOnCapturedContext = false) where TException : Exception => HandleSafeFireAndForget(task, continueOnCapturedContext, onException);
3139

40+
/// <summary>
41+
/// Safely execute the ValueTask without waiting for it to complete before moving to the next line of code; commonly known as "Fire And Forget". Inspired by John Thiriet's blog post, "Removing Async Void": https://johnthiriet.com/removing-async-void/.
42+
/// </summary>
43+
/// <param name="task">ValueTask.</param>
44+
/// <param name="onException">If an exception is thrown in the Task, <c>onException</c> will execute. If onException is null, the exception will be re-thrown</param>
45+
/// <param name="continueOnCapturedContext">If set to <c>true</c>, continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to <c>false</c>, continue on a different context; this will allow the Synchronization Context to continue on a different thread</param>
46+
/// <typeparam name="T">The return value of the ValueTask.</typeparam>
47+
/// <typeparam name="TException">Exception type. If an exception is thrown of a different type, it will not be handled</typeparam>
48+
public static void SafeFireAndForget<T, TException>(this ValueTask<T> task, in Action<TException>? onException = null, in bool continueOnCapturedContext = false) where TException : Exception => HandleSafeFireAndForget(task, continueOnCapturedContext, onException);
49+
3250
#if NET8_0_OR_GREATER
3351
/// <summary>
3452
/// Safely execute the Task without waiting for it to complete before moving to the next line of code; commonly known as "Fire And Forget". Inspired by John Thiriet's blog post, "Removing Async Void": https://johnthiriet.com/removing-async-void/.
@@ -105,6 +123,21 @@ static async void HandleSafeFireAndForget<TException>(ValueTask valueTask, bool
105123
}
106124
}
107125

126+
static async void HandleSafeFireAndForget<T, TException>(ValueTask<T> valueTask, bool continueOnCapturedContext, Action<TException>? onException) where TException : Exception
127+
{
128+
try
129+
{
130+
await valueTask.ConfigureAwait(continueOnCapturedContext);
131+
}
132+
catch (TException ex) when (_onException is not null || onException is not null)
133+
{
134+
HandleException(ex, onException);
135+
136+
if (_shouldAlwaysRethrowException)
137+
throw;
138+
}
139+
}
140+
108141
static async void HandleSafeFireAndForget<TException>(Task task, bool continueOnCapturedContext, Action<TException>? onException) where TException : Exception
109142
{
110143
try

0 commit comments

Comments
 (0)