Skip to content

Commit 6ef4fe6

Browse files
Add Support for ConfigureAwaitOptions in .NET 8 (#189)
* Add `ConfigureAwaitOptions` * Update README.md * Update README.md * Fix File Naming * Update to v7.0.0 * `dotnet format`
1 parent 0be41b3 commit 6ef4fe6

13 files changed

+449
-26
lines changed

Directory.Build.props

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Project>
3-
<PropertyGroup>
3+
<PropertyGroup>
44
<!-- Fixes https://github.com/dotnet/maui/pull/12114 -->
55
<PublishReadyToRun>false</PublishReadyToRun>
6-
<Nullable>enable</Nullable>
7-
<LangVersion>preview</LangVersion>
8-
<Deterministic>true</Deterministic>
9-
<LatestSupportedTFM>net8.0</LatestSupportedTFM>
10-
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
11-
<ProduceReferenceAssembly>True</ProduceReferenceAssembly>
6+
<Nullable>enable</Nullable>
7+
<NuGetVersion>7.0.0</NuGetVersion>
8+
<LangVersion>preview</LangVersion>
9+
<Deterministic>true</Deterministic>
10+
<LatestSupportedTFM>net8.0</LatestSupportedTFM>
11+
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
12+
<ProduceReferenceAssembly>True</ProduceReferenceAssembly>
1213
<AccelerateBuildsInVisualStudio>true</AccelerateBuildsInVisualStudio>
13-
<GenerateErrorForMissingTargetingPacks>false</GenerateErrorForMissingTargetingPacks>
14-
<!-- WarningsAsErrors
14+
<GenerateErrorForMissingTargetingPacks>false</GenerateErrorForMissingTargetingPacks>
15+
<!-- WarningsAsErrors
1516
CS0419: Ambiguous reference in cref attribute
1617
CS1570: XML comment has badly formed XML 'Expected an end tag for element [parameter]
1718
CS1571: XML comment on [construct] has a duplicate param tag for [parameter]
@@ -27,6 +28,6 @@
2728
CS1598: XML parser could not be loaded. The XML documentation file will not be generated.
2829
CS1658: Identifier expected; 'true' is a keyword
2930
CS1734: XML comment has a paramref tag, but there is no parameter by that name -->
30-
<WarningsAsErrors>nullable,CS0419,CS1570,CS1571,CS1572,CS1573,CS1574,CS1580,CS1581,CS1584,CS1589,CS1590,CS1592,CS1598,CS1658,CS1734</WarningsAsErrors>
31-
</PropertyGroup>
31+
<WarningsAsErrors>nullable,CS0419,CS1570,CS1571,CS1572,CS1573,CS1574,CS1580,CS1581,CS1584,CS1589,CS1590,CS1592,CS1598,CS1658,CS1734</WarningsAsErrors>
32+
</PropertyGroup>
3233
</Project>

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,24 @@ public static async void SafeFireAndForget(this System.Threading.Tasks.Task task
145145
public static async void SafeFireAndForget(this System.Threading.Tasks.ValueTask task, System.Action<System.Exception>? onException = null, bool continueOnCapturedContext = false)
146146
```
147147

148+
#### On .NET 8.0 (and higher)
149+
150+
.NET 8.0 Introduces [`ConfigureAwaitOptions`](https://learn.microsoft.com/dotnet/api/system.threading.tasks.configureawaitoptions) that allow users to customize the behavior when awaiting:
151+
- `ConfigureAwaitOptions.None`
152+
- No options specified
153+
- `ConfigureAwaitOptions.SuppressThrowing`
154+
- Avoids throwing an exception at the completion of awaiting a Task that ends in the Faulted or Canceled state
155+
- `ConfigureAwaitOptions.ContinueOnCapturedContext`
156+
- Attempts to marshal the continuation back to the original SynchronizationContext or TaskScheduler present on the originating thread at the time of the await
157+
- `ConfigureAwaitOptions.ForceYielding`
158+
- Forces an await on an already completed Task to behave as if the Task wasn't yet completed, such that the current asynchronous method will be forced to yield its execution
159+
160+
For more information, check out Stephen Cleary's blog post, ["ConfigureAwait in .NET 8"](https://blog.stephencleary.com/2023/11/configureawait-in-net-8.html).
161+
162+
```csharp
163+
public static void SafeFireAndForget(this System.Threading.Tasks.Task task, ConfigureAwaitOptions configureAwaitOptions, Action<Exception>? onException = null)
164+
```
165+
148166
#### Basic Usage - Task
149167

150168
```csharp
@@ -164,6 +182,8 @@ async Task ExampleAsyncMethod()
164182
}
165183
```
166184

185+
> **Note:** `ConfigureAwaitOptions.SuppressThrowing` will always supress exceptions from being rethrown. This means that `onException` will never execute when `ConfigureAwaitOptions.SuppressThrowing` is set.
186+
167187
#### Basic Usage - ValueTask
168188

169189
If you're new to ValueTask, check out this great write-up, [Understanding the Whys, Whats, and Whens of ValueTask
@@ -243,6 +263,8 @@ async ValueTask ExampleValueTaskMethod()
243263
}
244264
```
245265

266+
> **Note:** `ConfigureAwaitOptions.SuppressThrowing` will always supress exceptions from being rethrown. This means that `onException` will never execute when `ConfigureAwaitOptions.SuppressThrowing` is set.
267+
246268
### `WeakEventManager`
247269

248270
An event implementation that enables the [garbage collector to collect an object without needing to unsubscribe event handlers](http://paulstovell.com/blog/weakevents).

azure-pipelines.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
variables:
22
DOTNET_CLI_TELEMETRY_OPTOUT: true
3-
CurrentSemanticVersionBase: '1.0.0'
3+
CurrentSemanticVersionBase: '99.0.0'
44
PreviewNumber: $[counter(variables['CurrentSemanticVersionBase'], 1001)]
55
CurrentSemanticVersion: '$(CurrentSemanticVersionBase)-preview$(PreviewNumber)'
66
NugetPackageVersion: '$(CurrentSemanticVersion)'

sample/HackerNews.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<UseMaui>true</UseMaui>
99
<SingleProject>true</SingleProject>
1010
<ImplicitUsings>enable</ImplicitUsings>
11+
<IsPackable>false</IsPackable>
1112

1213
<ApplicationTitle>HackerNews</ApplicationTitle>
1314

src/AsyncAwaitBestPractices.MVVM/AsyncAwaitBestPractices.MVVM.csproj

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<Project Sdk="Microsoft.NET.Sdk">
33
<PropertyGroup>
4-
<TargetFrameworks>netstandard1.0;netstandard2.0;netstandard2.1;net6.0;net7.0</TargetFrameworks>
4+
<TargetFrameworks>netstandard1.0;netstandard2.0;netstandard2.1;net6.0;net7.0;net8.0</TargetFrameworks>
55
<SignAssembly>true</SignAssembly>
66
<AssemblyOriginatorKeyFile>AsyncAwaitBestPracticesMVVM.snk</AssemblyOriginatorKeyFile>
77
<AssemblyName>AsyncAwaitBestPractices.MVVM</AssemblyName>
@@ -25,19 +25,19 @@
2525
</Description>
2626
<PackageReleaseNotes>
2727
New in this release:
28-
- Add .NET 7.0 Target
28+
- Add .NET 8.0 Target
2929
</PackageReleaseNotes>
30-
<Version>6.0.6</Version>
30+
<Version>$(NuGetVersion)</Version>
3131
<PackageReadmeFile>README.md</PackageReadmeFile>
3232
<RepositoryUrl>https://github.com/brminnick/AsyncAwaitBestPractices</RepositoryUrl>
3333
<Product>$(AssemblyName) ($(TargetFramework))</Product>
34-
<AssemblyVersion>6.0.5</AssemblyVersion>
35-
<AssemblyFileVersion>6.0.5</AssemblyFileVersion>
34+
<AssemblyVersion>$(NuGetVersion)</AssemblyVersion>
35+
<AssemblyFileVersion>$(NuGetVersion)</AssemblyFileVersion>
3636
<PackageVersion>$(Version)$(VersionSuffix)</PackageVersion>
3737
<Authors>Brandon Minnick, John Thiriet</Authors>
3838
<Owners>Brandon Minnick</Owners>
3939
<NeutralLanguage>en</NeutralLanguage>
40-
<Copyright>©Copyright 2021 Brandon Minnick. All rights reserved.</Copyright>
40+
<Copyright>©Copyright 2023 Brandon Minnick. All rights reserved.</Copyright>
4141
<requireLicenseAcceptance>false</requireLicenseAcceptance>
4242
<DefineConstants>$(DefineConstants);</DefineConstants>
4343
<UseFullSemVerForNuGet>false</UseFullSemVerForNuGet>

src/AsyncAwaitBestPractices.UnitTests/AsyncAwaitBestPractices.UnitTests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<Project Sdk="Microsoft.NET.Sdk">
33
<PropertyGroup>
4-
<TargetFrameworks>net45;netcoreapp2.1;netcoreapp3.0;net6.0;net7.0</TargetFrameworks>
4+
<TargetFrameworks>net45;netcoreapp2.1;netcoreapp3.0;net6.0;net7.0;net8.0</TargetFrameworks>
55
<IsPackable>false</IsPackable>
66
</PropertyGroup>
77
<ItemGroup>

src/AsyncAwaitBestPractices.UnitTests/BaseTest.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
using System;
2+
using System.Diagnostics.CodeAnalysis;
23
using System.Threading.Tasks;
34

45
namespace AsyncAwaitBestPractices.UnitTests;
56

7+
[ExcludeFromCodeCoverage]
68
abstract class BaseTest
79
{
810
protected event EventHandler TestEvent

src/AsyncAwaitBestPractices.UnitTests/SafeFireAndForgetTests/Tests_Task_SafeFIreAndForgetT.cs renamed to src/AsyncAwaitBestPractices.UnitTests/SafeFireAndForgetTests/Tests_Task_SafeFireAndForgetT.cs

File renamed without changes.
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
#if NET8_0_OR_GREATER
2+
using System;
3+
using System.Threading.Tasks;
4+
using NUnit.Framework;
5+
6+
namespace AsyncAwaitBestPractices.UnitTests;
7+
8+
class Tests_Task_SafeFIreAndForgetT_ConfigureAwaitOptions : BaseTest
9+
{
10+
[SetUp]
11+
public void BeforeEachTest()
12+
{
13+
SafeFireAndForgetExtensions.Initialize(false);
14+
SafeFireAndForgetExtensions.RemoveDefaultExceptionHandling();
15+
}
16+
17+
[TearDown]
18+
public void AfterEachTest()
19+
{
20+
SafeFireAndForgetExtensions.Initialize(false);
21+
SafeFireAndForgetExtensions.RemoveDefaultExceptionHandling();
22+
}
23+
24+
[Test]
25+
public async Task SafeFireAndForget_HandledException()
26+
{
27+
//Arrange
28+
NullReferenceException? exception = null;
29+
30+
//Act
31+
NoParameterDelayedNullReferenceExceptionTask().SafeFireAndForget<NullReferenceException>(ConfigureAwaitOptions.None, ex => exception = ex);
32+
await NoParameterTask();
33+
await NoParameterTask();
34+
35+
//Assert
36+
Assert.IsNotNull(exception);
37+
}
38+
39+
[Test]
40+
public async Task SafeFireAndForget_HandledException_ConfigureAwaitOptionsSuppressThrowing()
41+
{
42+
//Arrange
43+
NullReferenceException? exception = null;
44+
45+
//Act
46+
NoParameterDelayedNullReferenceExceptionTask().SafeFireAndForget<NullReferenceException>(ConfigureAwaitOptions.SuppressThrowing, ex => exception = ex);
47+
await NoParameterTask();
48+
await NoParameterTask();
49+
50+
//Assert
51+
Assert.IsNull(exception);
52+
}
53+
54+
[Test]
55+
public async Task SafeFireAndForgetT_SetDefaultExceptionHandling_NoParams()
56+
{
57+
//Arrange
58+
Exception? exception = null;
59+
SafeFireAndForgetExtensions.SetDefaultExceptionHandling(ex => exception = ex);
60+
61+
//Act
62+
NoParameterDelayedNullReferenceExceptionTask().SafeFireAndForget(ConfigureAwaitOptions.None);
63+
await NoParameterTask();
64+
await NoParameterTask();
65+
66+
//Assert
67+
Assert.IsNotNull(exception);
68+
}
69+
70+
[Test]
71+
public async Task SafeFireAndForgetT_SetDefaultExceptionHandling_ConfigureAwaitOptionsSuppressThrowing()
72+
{
73+
//Arrange
74+
Exception? exception = null;
75+
SafeFireAndForgetExtensions.SetDefaultExceptionHandling(ex => exception = ex);
76+
77+
//Act
78+
NoParameterDelayedNullReferenceExceptionTask().SafeFireAndForget(ConfigureAwaitOptions.SuppressThrowing);
79+
await NoParameterTask();
80+
await NoParameterTask();
81+
82+
//Assert
83+
Assert.IsNull(exception);
84+
}
85+
86+
[Test]
87+
public async Task SafeFireAndForgetT_SetDefaultExceptionHandling_WithParams()
88+
{
89+
//Arrange
90+
Exception? exception1 = null;
91+
NullReferenceException? exception2 = null;
92+
SafeFireAndForgetExtensions.SetDefaultExceptionHandling(ex => exception1 = ex);
93+
94+
//Act
95+
NoParameterDelayedNullReferenceExceptionTask().SafeFireAndForget<NullReferenceException>(ConfigureAwaitOptions.None, ex => exception2 = ex);
96+
await NoParameterTask();
97+
await NoParameterTask();
98+
99+
//Assert
100+
Assert.IsNotNull(exception1);
101+
Assert.IsNotNull(exception2);
102+
}
103+
104+
[Test]
105+
public async Task SafeFireAndForgetT_SetDefaultExceptionHandling_WithParams_ConfigureAwaitOptionsSuppressThrowing()
106+
{
107+
//Arrange
108+
Exception? exception1 = null;
109+
NullReferenceException? exception2 = null;
110+
SafeFireAndForgetExtensions.SetDefaultExceptionHandling(ex => exception1 = ex);
111+
112+
//Act
113+
NoParameterDelayedNullReferenceExceptionTask().SafeFireAndForget<NullReferenceException>(ConfigureAwaitOptions.SuppressThrowing, ex => exception2 = ex);
114+
await NoParameterTask();
115+
await NoParameterTask();
116+
117+
//Assert
118+
Assert.IsNull(exception1);
119+
Assert.IsNull(exception2);
120+
}
121+
}
122+
#endif

0 commit comments

Comments
 (0)