Skip to content

Commit

Permalink
Add Support for ConfigureAwaitOptions in .NET 8 (#189)
Browse files Browse the repository at this point in the history
* Add `ConfigureAwaitOptions`

* Update README.md

* Update README.md

* Fix File Naming

* Update to v7.0.0

* `dotnet format`
  • Loading branch information
TheCodeTraveler authored Nov 13, 2023
1 parent 0be41b3 commit 6ef4fe6
Show file tree
Hide file tree
Showing 13 changed files with 449 additions and 26 deletions.
23 changes: 12 additions & 11 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<Project>
<PropertyGroup>
<PropertyGroup>
<!-- Fixes https://github.com/dotnet/maui/pull/12114 -->
<PublishReadyToRun>false</PublishReadyToRun>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
<Deterministic>true</Deterministic>
<LatestSupportedTFM>net8.0</LatestSupportedTFM>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<ProduceReferenceAssembly>True</ProduceReferenceAssembly>
<Nullable>enable</Nullable>
<NuGetVersion>7.0.0</NuGetVersion>
<LangVersion>preview</LangVersion>
<Deterministic>true</Deterministic>
<LatestSupportedTFM>net8.0</LatestSupportedTFM>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<ProduceReferenceAssembly>True</ProduceReferenceAssembly>
<AccelerateBuildsInVisualStudio>true</AccelerateBuildsInVisualStudio>
<GenerateErrorForMissingTargetingPacks>false</GenerateErrorForMissingTargetingPacks>
<!-- WarningsAsErrors
<GenerateErrorForMissingTargetingPacks>false</GenerateErrorForMissingTargetingPacks>
<!-- WarningsAsErrors
CS0419: Ambiguous reference in cref attribute
CS1570: XML comment has badly formed XML 'Expected an end tag for element [parameter]
CS1571: XML comment on [construct] has a duplicate param tag for [parameter]
Expand All @@ -27,6 +28,6 @@
CS1598: XML parser could not be loaded. The XML documentation file will not be generated.
CS1658: Identifier expected; 'true' is a keyword
CS1734: XML comment has a paramref tag, but there is no parameter by that name -->
<WarningsAsErrors>nullable,CS0419,CS1570,CS1571,CS1572,CS1573,CS1574,CS1580,CS1581,CS1584,CS1589,CS1590,CS1592,CS1598,CS1658,CS1734</WarningsAsErrors>
</PropertyGroup>
<WarningsAsErrors>nullable,CS0419,CS1570,CS1571,CS1572,CS1573,CS1574,CS1580,CS1581,CS1584,CS1589,CS1590,CS1592,CS1598,CS1658,CS1734</WarningsAsErrors>
</PropertyGroup>
</Project>
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,24 @@ public static async void SafeFireAndForget(this System.Threading.Tasks.Task task
public static async void SafeFireAndForget(this System.Threading.Tasks.ValueTask task, System.Action<System.Exception>? onException = null, bool continueOnCapturedContext = false)
```

#### On .NET 8.0 (and higher)

.NET 8.0 Introduces [`ConfigureAwaitOptions`](https://learn.microsoft.com/dotnet/api/system.threading.tasks.configureawaitoptions) that allow users to customize the behavior when awaiting:
- `ConfigureAwaitOptions.None`
- No options specified
- `ConfigureAwaitOptions.SuppressThrowing`
- Avoids throwing an exception at the completion of awaiting a Task that ends in the Faulted or Canceled state
- `ConfigureAwaitOptions.ContinueOnCapturedContext`
- Attempts to marshal the continuation back to the original SynchronizationContext or TaskScheduler present on the originating thread at the time of the await
- `ConfigureAwaitOptions.ForceYielding`
- 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

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).

```csharp
public static void SafeFireAndForget(this System.Threading.Tasks.Task task, ConfigureAwaitOptions configureAwaitOptions, Action<Exception>? onException = null)
```

#### Basic Usage - Task

```csharp
Expand All @@ -164,6 +182,8 @@ async Task ExampleAsyncMethod()
}
```

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

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

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

An event implementation that enables the [garbage collector to collect an object without needing to unsubscribe event handlers](http://paulstovell.com/blog/weakevents).
Expand Down
2 changes: 1 addition & 1 deletion azure-pipelines.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
variables:
DOTNET_CLI_TELEMETRY_OPTOUT: true
CurrentSemanticVersionBase: '1.0.0'
CurrentSemanticVersionBase: '99.0.0'
PreviewNumber: $[counter(variables['CurrentSemanticVersionBase'], 1001)]
CurrentSemanticVersion: '$(CurrentSemanticVersionBase)-preview$(PreviewNumber)'
NugetPackageVersion: '$(CurrentSemanticVersion)'
Expand Down
1 change: 1 addition & 0 deletions sample/HackerNews.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<UseMaui>true</UseMaui>
<SingleProject>true</SingleProject>
<ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable>

<ApplicationTitle>HackerNews</ApplicationTitle>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard1.0;netstandard2.0;netstandard2.1;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks>netstandard1.0;netstandard2.0;netstandard2.1;net6.0;net7.0;net8.0</TargetFrameworks>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>AsyncAwaitBestPracticesMVVM.snk</AssemblyOriginatorKeyFile>
<AssemblyName>AsyncAwaitBestPractices.MVVM</AssemblyName>
Expand All @@ -25,19 +25,19 @@
</Description>
<PackageReleaseNotes>
New in this release:
- Add .NET 7.0 Target
- Add .NET 8.0 Target
</PackageReleaseNotes>
<Version>6.0.6</Version>
<Version>$(NuGetVersion)</Version>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/brminnick/AsyncAwaitBestPractices</RepositoryUrl>
<Product>$(AssemblyName) ($(TargetFramework))</Product>
<AssemblyVersion>6.0.5</AssemblyVersion>
<AssemblyFileVersion>6.0.5</AssemblyFileVersion>
<AssemblyVersion>$(NuGetVersion)</AssemblyVersion>
<AssemblyFileVersion>$(NuGetVersion)</AssemblyFileVersion>
<PackageVersion>$(Version)$(VersionSuffix)</PackageVersion>
<Authors>Brandon Minnick, John Thiriet</Authors>
<Owners>Brandon Minnick</Owners>
<NeutralLanguage>en</NeutralLanguage>
<Copyright>©Copyright 2021 Brandon Minnick. All rights reserved.</Copyright>
<Copyright>©Copyright 2023 Brandon Minnick. All rights reserved.</Copyright>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<DefineConstants>$(DefineConstants);</DefineConstants>
<UseFullSemVerForNuGet>false</UseFullSemVerForNuGet>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net45;netcoreapp2.1;netcoreapp3.0;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks>net45;netcoreapp2.1;netcoreapp3.0;net6.0;net7.0;net8.0</TargetFrameworks>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
Expand Down
2 changes: 2 additions & 0 deletions src/AsyncAwaitBestPractices.UnitTests/BaseTest.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;

namespace AsyncAwaitBestPractices.UnitTests;

[ExcludeFromCodeCoverage]
abstract class BaseTest
{
protected event EventHandler TestEvent
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#if NET8_0_OR_GREATER
using System;
using System.Threading.Tasks;
using NUnit.Framework;

namespace AsyncAwaitBestPractices.UnitTests;

class Tests_Task_SafeFIreAndForgetT_ConfigureAwaitOptions : BaseTest
{
[SetUp]
public void BeforeEachTest()
{
SafeFireAndForgetExtensions.Initialize(false);
SafeFireAndForgetExtensions.RemoveDefaultExceptionHandling();
}

[TearDown]
public void AfterEachTest()
{
SafeFireAndForgetExtensions.Initialize(false);
SafeFireAndForgetExtensions.RemoveDefaultExceptionHandling();
}

[Test]
public async Task SafeFireAndForget_HandledException()
{
//Arrange
NullReferenceException? exception = null;

//Act
NoParameterDelayedNullReferenceExceptionTask().SafeFireAndForget<NullReferenceException>(ConfigureAwaitOptions.None, ex => exception = ex);
await NoParameterTask();
await NoParameterTask();

//Assert
Assert.IsNotNull(exception);
}

[Test]
public async Task SafeFireAndForget_HandledException_ConfigureAwaitOptionsSuppressThrowing()
{
//Arrange
NullReferenceException? exception = null;

//Act
NoParameterDelayedNullReferenceExceptionTask().SafeFireAndForget<NullReferenceException>(ConfigureAwaitOptions.SuppressThrowing, ex => exception = ex);
await NoParameterTask();
await NoParameterTask();

//Assert
Assert.IsNull(exception);
}

[Test]
public async Task SafeFireAndForgetT_SetDefaultExceptionHandling_NoParams()
{
//Arrange
Exception? exception = null;
SafeFireAndForgetExtensions.SetDefaultExceptionHandling(ex => exception = ex);

//Act
NoParameterDelayedNullReferenceExceptionTask().SafeFireAndForget(ConfigureAwaitOptions.None);
await NoParameterTask();
await NoParameterTask();

//Assert
Assert.IsNotNull(exception);
}

[Test]
public async Task SafeFireAndForgetT_SetDefaultExceptionHandling_ConfigureAwaitOptionsSuppressThrowing()
{
//Arrange
Exception? exception = null;
SafeFireAndForgetExtensions.SetDefaultExceptionHandling(ex => exception = ex);

//Act
NoParameterDelayedNullReferenceExceptionTask().SafeFireAndForget(ConfigureAwaitOptions.SuppressThrowing);
await NoParameterTask();
await NoParameterTask();

//Assert
Assert.IsNull(exception);
}

[Test]
public async Task SafeFireAndForgetT_SetDefaultExceptionHandling_WithParams()
{
//Arrange
Exception? exception1 = null;
NullReferenceException? exception2 = null;
SafeFireAndForgetExtensions.SetDefaultExceptionHandling(ex => exception1 = ex);

//Act
NoParameterDelayedNullReferenceExceptionTask().SafeFireAndForget<NullReferenceException>(ConfigureAwaitOptions.None, ex => exception2 = ex);
await NoParameterTask();
await NoParameterTask();

//Assert
Assert.IsNotNull(exception1);
Assert.IsNotNull(exception2);
}

[Test]
public async Task SafeFireAndForgetT_SetDefaultExceptionHandling_WithParams_ConfigureAwaitOptionsSuppressThrowing()
{
//Arrange
Exception? exception1 = null;
NullReferenceException? exception2 = null;
SafeFireAndForgetExtensions.SetDefaultExceptionHandling(ex => exception1 = ex);

//Act
NoParameterDelayedNullReferenceExceptionTask().SafeFireAndForget<NullReferenceException>(ConfigureAwaitOptions.SuppressThrowing, ex => exception2 = ex);
await NoParameterTask();
await NoParameterTask();

//Assert
Assert.IsNull(exception1);
Assert.IsNull(exception2);
}
}
#endif
Loading

0 comments on commit 6ef4fe6

Please sign in to comment.