AsyncAwaitBestPractices 6.0.4
AsyncAwaitBestPractices
Available on NuGet: https://www.nuget.org/packages/AsyncAwaitBestPractices/
SafeFireAndForget- An extension method to safely fire-and-forget a
Taskor aValueTask - Ensures the
Taskwill rethrow anExceptionif anExceptionis caught inIAsyncStateMachine.MoveNext()
- An extension method to safely fire-and-forget a
WeakEventManager- Avoids memory leaks when events are not unsubscribed
- Used by
AsyncCommand,AsyncCommand<T>,AsyncValueCommand,AsyncValueCommand<T>
- Usage instructions
Setup
- Available on NuGet: https://www.nuget.org/packages/AsyncAwaitBestPractices/
- Add to any project supporting .NET Standard 1.0
Usage
SafeFireAndForget
An extension method to safely fire-and-forget a Task.
SafeFireAndForget allows a Task to safely run on a different thread while the calling thread does not wait for its completion.
public static async void SafeFireAndForget(this System.Threading.Tasks.Task task, System.Action<System.Exception>? onException = null, bool continueOnCapturedContext = false)
public static async void SafeFireAndForget(this System.Threading.Tasks.ValueTask task, System.Action<System.Exception>? onException = null, bool continueOnCapturedContext = false)
Basic Usage - Task
void HandleButtonTapped(object sender, EventArgs e)
{
// Allows the async Task method to safely run on a different thread while the calling thread continues, not awaiting its completion
// onException: If an Exception is thrown, print it to the Console
ExampleAsyncMethod().SafeFireAndForget(onException: ex => Console.WriteLine(ex));
// HandleButtonTapped continues execution here while `ExampleAsyncMethod()` is running on a different thread
// ...
}
async Task ExampleAsyncMethod()
{
await Task.Delay(1000);
}
Basic Usage - ValueTask
If you're new to ValueTask, check out this great write-up, Understanding the Whys, Whats, and Whens of ValueTask.
void HandleButtonTapped(object sender, EventArgs e)
{
// Allows the async ValueTask method to safely run on a different thread while the calling thread continues, not awaiting its completion
// onException: If an Exception is thrown, print it to the Console
ExampleValueTaskMethod().SafeFireAndForget(onException: ex => Console.WriteLine(ex));
// HandleButtonTapped continues execution here while `ExampleAsyncMethod()` is running on a different thread
// ...
}
async ValueTask ExampleValueTaskMethod()
{
var random = new Random();
if (random.Next(10) > 9)
await Task.Delay(1000);
}
Advanced Usage
void InitializeSafeFireAndForget()
{
// Initialize SafeFireAndForget
// Only use `shouldAlwaysRethrowException: true` when you want `.SafeFireAndForget()` to always rethrow every exception. This is not recommended, because there is no way to catch an Exception rethrown by `SafeFireAndForget()`; `shouldAlwaysRethrowException: true` should **not** be used in Production/Release builds.
SafeFireAndForgetExtensions.Initialize(shouldAlwaysRethrowException: false);
// SafeFireAndForget will print every exception to the Console
SafeFireAndForgetExtensions.SetDefaultExceptionHandling(ex => Console.WriteLine(ex));
}
void UninitializeSafeFireAndForget()
{
// Remove default exception handling
SafeFireAndForgetExtensions.RemoveDefaultExceptionHandling()
}
void HandleButtonTapped(object sender, EventArgs e)
{
// Allows the async Task method to safely run on a different thread while not awaiting its completion
// onException: If a WebException is thrown, print its StatusCode to the Console. **Note**: If a non-WebException is thrown, it will not be handled by `onException`
// Because we set `SetDefaultExceptionHandling` in `void InitializeSafeFireAndForget()`, the entire exception will also be printed to the Console
ExampleAsyncMethod().SafeFireAndForget<WebException>(onException: ex =>
{
if(ex.Response is HttpWebResponse webResponse)
Console.WriteLine($"Task Exception\n Status Code: {webResponse.StatusCode}");
});
ExampleValueTaskMethod().SafeFireAndForget<WebException>(onException: ex =>
{
if(ex.Response is HttpWebResponse webResponse)
Console.WriteLine($"ValueTask Error\n Status Code: {webResponse.StatusCode}");
});
// HandleButtonTapped continues execution here while `ExampleAsyncMethod()` and `ExampleValueTaskMethod()` run in the background
}
async Task ExampleAsyncMethod()
{
await Task.Delay(1000);
throw new WebException();
}
async ValueTask ExampleValueTaskMethod()
{
var random = new Random();
if (random.Next(10) > 9)
await Task.Delay(1000);
throw new WebException();
}
WeakEventManager
An event implementation that enables the garbage collector to collect an object without needing to unsubscribe event handlers.
Inspired by Xamarin.Forms.WeakEventManager.
Using EventHandler
readonly WeakEventManager _canExecuteChangedEventManager = new WeakEventManager();
public event EventHandler CanExecuteChanged
{
add => _canExecuteChangedEventManager.AddEventHandler(value);
remove => _canExecuteChangedEventManager.RemoveEventHandler(value);
}
void OnCanExecuteChanged() => _canExecuteChangedEventManager.RaiseEvent(this, EventArgs.Empty, nameof(CanExecuteChanged));
Using Delegate
readonly WeakEventManager _propertyChangedEventManager = new WeakEventManager();
public event PropertyChangedEventHandler PropertyChanged
{
add => _propertyChangedEventManager.AddEventHandler(value);
remove => _propertyChangedEventManager.RemoveEventHandler(value);
}
void OnPropertyChanged([CallerMemberName]string propertyName = "") => _propertyChangedEventManager.RaiseEvent(this, new PropertyChangedEventArgs(propertyName), nameof(PropertyChanged));
Using Action
readonly WeakEventManager _weakActionEventManager = new WeakEventManager();
public event Action ActionEvent
{
add => _weakActionEventManager.AddEventHandler(value);
remove => _weakActionEventManager.RemoveEventHandler(value);
}
void OnActionEvent(string message) => _weakActionEventManager.RaiseEvent(message, nameof(ActionEvent));
WeakEventManager<T>
An event implementation that enables the garbage collector to collect an object without needing to unsubscribe event handlers.
Inspired by Xamarin.Forms.WeakEventManager.
Using EventHandler<T>
readonly WeakEventManager<string> _errorOcurredEventManager = new WeakEventManager<string>();
public event EventHandler<string> ErrorOcurred
{
add => _errorOcurredEventManager.AddEventHandler(value);
remove => _errorOcurredEventManager.RemoveEventHandler(value);
}
void OnErrorOcurred(string message) => _errorOcurredEventManager.RaiseEvent(this, message, nameof(ErrorOcurred));
Using Action<T>
readonly WeakEventManager<string> _weakActionEventManager = new WeakEventManager<string>();
public event Action<string> ActionEvent
{
add => _weakActionEventManager.AddEventHandler(value);
remove => _weakActionEventManager.RemoveEventHandler(value);
}
void OnActionEvent(string message) => _weakActionEventManager.RaiseEvent(message, nameof(ActionEvent));
No packages depend on AsyncAwaitBestPractices.
New In This Release:
- Mark internal classes as abstract
- Update AssemblyFileVersion
- Add NuGet README
- Improve Nullablitiy
.NET Standard 1.0
- System.Reflection.Emit.Lightweight (>= 4.7.0)
- System.Threading.Tasks.Extensions (>= 4.5.4)
- System.ValueTuple (>= 4.5.0)
.NET Standard 2.0
- System.Reflection.Emit.Lightweight (>= 4.7.0)
- System.Threading.Tasks.Extensions (>= 4.5.4)
.NET Standard 2.1
- No dependencies.
| Version | Downloads | Last updated |
|---|---|---|
| 10.0.0 | 0 | 2025-11-11 |
| 9.0.0 | 0 | 2024-11-15 |
| 8.0.0 | 0 | 2024-07-09 |
| 7.0.0 | 0 | 2023-11-14 |
| 6.0.6 | 0 | 2022-11-12 |
| 6.0.5 | 0 | 2022-07-03 |
| 6.0.4 | 1 | 2025-08-13 |
| 6.0.3 | 0 | 2021-11-11 |
| 6.0.2 | 0 | 2021-10-12 |
| 6.0.1 | 0 | 2021-09-27 |
| 6.0.0 | 0 | 2021-07-03 |
| 6.0.0-pre1 | 0 | 2021-06-07 |
| 5.1.0 | 0 | 2021-03-13 |
| 5.0.2 | 0 | 2020-11-02 |
| 5.0.0-pre2 | 0 | 2020-09-17 |
| 5.0.0-pre1 | 0 | 2020-09-17 |
| 4.3.0 | 0 | 2020-09-15 |
| 4.3.0-pre1 | 0 | 2020-07-29 |
| 4.2.0 | 0 | 2020-07-13 |
| 4.1.1 | 0 | 2020-05-15 |
| 4.1.1-pre1 | 0 | 2020-04-01 |
| 4.1.0 | 0 | 2020-01-30 |
| 4.1.0-pre2 | 0 | 2020-01-07 |
| 4.1.0-pre1 | 0 | 2019-12-19 |
| 4.0.1 | 0 | 2019-12-13 |
| 4.0.0-pre3 | 0 | 2019-11-29 |
| 4.0.0-pre1 | 0 | 2019-11-07 |
| 3.1.0 | 0 | 2019-08-28 |
| 3.1.0-pre5 | 0 | 2019-08-20 |
| 3.1.0-pre4 | 0 | 2019-08-20 |
| 3.1.0-pre3 | 0 | 2019-08-14 |
| 3.1.0-pre2 | 0 | 2019-07-31 |
| 3.1.0-pre1 | 0 | 2019-07-31 |
| 3.0.0 | 0 | 2019-07-30 |
| 3.0.0-pre4 | 0 | 2019-07-14 |
| 3.0.0-pre3 | 0 | 2019-07-07 |
| 3.0.0-pre2 | 0 | 2019-07-02 |
| 3.0.0-pre1 | 0 | 2019-06-09 |
| 2.1.1 | 0 | 2019-04-17 |
| 2.1.0 | 0 | 2019-01-12 |
| 2.1.0-pre1 | 0 | 2018-12-27 |
| 2.0.0 | 0 | 2018-12-19 |
| 1.2.1 | 0 | 2018-12-17 |
| 1.2.0 | 0 | 2018-12-17 |
| 1.1.0 | 0 | 2018-12-16 |
| 1.0.1 | 0 | 2018-12-15 |
| 1.0.0 | 0 | 2018-11-30 |
| 0.9.0 | 0 | 2018-11-22 |