Use WeakEventManager to avoid memory leaks
mainWeakEventManager is used internally by AsyncCommand, AsyncCommand<T>, AsyncValueCommand, and AsyncValueCommand<T> to avoid memory leaks when events are not explicitly unsubscribed.repository·main·Indexed 23 days ago
https://github.com/thecodetraveler/asyncawaitbestpracticesA library providing extensions for System.Threading.Tasks.Task and MVVM command implementations to address common asynchronous programming pitfalls. It features SafeFireAndForget to prevent silent exceptions in fire-and-forget tasks, WeakEventManager to avoid memory leaks, and specialized async command implementations (AsyncCommand and AsyncValueCommand) for using Task and ValueTask with ICommand in MVVM patterns. Compatible with .NET Standard 1.0.
WeakEventManager is used internally by AsyncCommand, AsyncCommand<T>, AsyncValueCommand, and AsyncValueCommand<T> to avoid memory leaks when events are not explicitly unsubscribed.When using async methods, the compiler transforms them into an IAsyncStateMachine class. The MoveNext() method, which executes the method's logic, is automatically wrapped in a try/catch block by the compiler.
Because of this, exceptions thrown inside an async method are caught internally by the state machine. If the task is not awaited, the exception is caught but never surfaced to the developer, making debugging extremely difficult. SafeFireAndForget solves this by ensuring these exceptions are rethrown.
WeakEventManager is an implementation that allows the garbage collector to collect an object even if it has active event subscriptions. This is useful for avoiding memory leaks in scenarios where event handlers are not explicitly unsubscribed (e.g., in MVVM patterns or long-lived services).You can initialize the SafeFireAndForget extension methods with global settings for exception handling.
Use SafeFireAndForgetExtensions.Initialize to set whether exceptions should always be rethrown.
Warning: Setting shouldAlwaysRethrowException: true is not recommended for Production/Release builds because there is no way to catch exceptions rethrown by SafeFireAndForget().
Use SafeFireAndForgetExtensions.SetDefaultExceptionHandling to define a global handler for all fire-and-forget tasks.
void InitializeSafeFireAndForget()
{
// Initialize SafeFireAndForget
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 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();
}To use this library, add the AsyncAwaitBestPractices package to any project supporting .NET Standard 1.0 via NuGet.
Package URL: https://www.nuget.org/packages/AsyncAwaitBestPractices/
Install the core library to access extensions for System.Threading.Tasks.Task, such as SafeFireAndForget. This package is compatible with any project supporting .NET Standard 1.0.
https://www.nuget.org/packages/AsyncAwaitBestPractices/To implement a weak event pattern, use WeakEventManager or WeakEventManager<T> to manage the underlying event storage. You then expose a standard C# event that uses the manager's AddEventHandler and RemoveEventHandler methods.
EventHandler or Action with WeakEventManagerreadonly 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));EventHandler<T> or Action<T> with WeakEventManager<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));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));To ensure exceptions thrown in an async method are visible, use one of the following methods:
await keyword (Preferred): This allows the Task to run asynchronously on a different thread without locking the current thread.await DoSomethingAsync().GetAwaiter().GetResult(): This is an alternative, but less preferred than await.DoSomethingAsync().GetAwaiter().GetResult()Never use .Result or .Wait():
System.AggregateException, which obscures the actual error.Install the MVVM extension package to use Task and ValueTask asynchronously with ICommand. This package is compatible with any project supporting .NET Standard 1.0.
https://www.nuget.org/packages/AsyncAwaitBestPractices.MVVM/To use these asynchronous command implementations in your MVVM project, add the AsyncAwaitBestPractices.MVVM package from NuGet. It is compatible with any project supporting .NET Standard 1.0.
https://www.nuget.org/packages/AsyncAwaitBestPractices.MVVM/You can use the generic version of SafeFireAndForget<TException> to provide an exception handler that only triggers for a specific exception type. If a different exception type is thrown, it will fall back to the global handler set via SetDefaultExceptionHandling (if any).
void HandleButtonTapped(object sender, EventArgs e)
{
// Only handles WebException; other exceptions use the global handler
ExampleAsyncMethod().SafeFireAndForget<WebException>(onException: ex =>
{
if(ex.Response is HttpWebResponse webResponse)
Console.WriteLine($"Task Exception\n Status Code: {webResponse.StatusCode}");
});
}
async Task ExampleAsyncMethod()
{
await Task.Delay(1000);
throw new WebException();
}In .NET 8.0 and higher, you can use ConfigureAwaitOptions to customize how the background task behaves when it completes.
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. Note: When this is set, onException will never execute because exceptions are suppressed from being rethrown.ConfigureAwaitOptions.ContinueOnCapturedContext: Attempts to marshal the continuation back to the original SynchronizationContext or TaskScheduler present on the originating thread.ConfigureAwaitOptions.ForceYielding: Forces an await on an already completed Task to behave as if the Task wasn't yet completed, forcing the current asynchronous method to yield execution.public static void SafeFireAndForget(this System.Threading.Tasks.Task task, ConfigureAwaitOptions configureAwaitOptions, Action<Exception>? onException = null)