Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
brminnick committed Dec 13, 2019
2 parents 23c7693 + f3d4139 commit 561e937
Show file tree
Hide file tree
Showing 36 changed files with 1,060 additions and 118 deletions.
Empty file modified .gitignore
100644 → 100755
Empty file.
171 changes: 162 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ Inspired by [John Thiriet](https://github.com/johnthiriet)'s blog posts: [Removi
Available on NuGet: https://www.nuget.org/packages/AsyncAwaitBestPractices/

- `SafeFireAndForget`
- An extension method to safely fire-and-forget a `Task`
- An extension method to safely fire-and-forget a `Task` or a `ValueTask`
- Ensures the `Task` will rethrow an `Exception` if an `Exception` is caught in `IAsyncStateMachine.MoveNext()`
- `WeakEventManager`
- Avoids memory leaks when events are not unsubscribed
- Used by `AsyncCommand` and `AsyncCommand<T>`
- Used by `AsyncCommand`, `AsyncCommand<T>`, `AsyncValueCommand`, `AsyncValueCommand<T>`
- [Usage instructions](#asyncawaitbestpractices-3)

### AsyncAwaitBestPractices.MVVM
Expand All @@ -31,6 +31,12 @@ Available on NuGet: https://www.nuget.org/packages/AsyncAwaitBestPractices/
- `AsyncCommand : IAsyncCommand`
- `IAsyncCommand<T> : ICommand`
- `AsyncCommand<T> : IAsyncCommand<T>`

- Allows for `ValueTask` to safely be used asynchronously with `ICommand`:
- `IAsyncValueCommand : ICommand`
- `AsyncValueCommand : IAsyncValueCommand`
- `IAsyncValueCommand<T> : ICommand`
- `AsyncValueCommand<T> : IAsyncValueCommand<T>`
- [Usage instructions](#asyncawaitbestpracticesmvvm-2)

## Setup
Expand Down Expand Up @@ -129,7 +135,11 @@ An extension method to safely fire-and-forget a `Task`.
public static async void SafeFireAndForget(this System.Threading.Tasks.Task task, bool continueOnCapturedContext = false, System.Action<System.Exception> onException = null)
```

#### Basic Usage
```csharp
public static async void SafeFireAndForget(this System.Threading.Tasks.ValueTask task, bool continueOnCapturedContext = false, System.Action<System.Exception> onException = null)
```

#### Basic Usage - Task

```csharp
void HandleButtonTapped(object sender, EventArgs e)
Expand All @@ -148,6 +158,30 @@ async Task ExampleAsyncMethod()
}
```

#### Basic Usage - ValueTask

If you're new to ValueTask, check out this great write-up, [Understanding the Whys, Whats, and Whens of ValueTask
](https://blogs.msdn.microsoft.com/dotnet/2018/11/07/understanding-the-whys-whats-and-whens-of-valuetask?WT.mc_id=asyncawaitbestpractices-github-bramin).

```csharp
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

```csharp
Expand Down Expand Up @@ -175,17 +209,32 @@ void HandleButtonTapped(object sender, EventArgs e)
ExampleAsyncMethod().SafeFireAndForget<WebException>(onException: ex =>
{
if(ex.Response is HttpWebResponse webResponse)
Console.WriteLine($"Status Code: {webResponse.StatusCode}");
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()` is running on a different thread
// 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`
Expand Down Expand Up @@ -282,15 +331,15 @@ Allows for `Task` to safely be used asynchronously with `ICommand`:

```csharp
public AsyncCommand(Func<T, Task> execute,
Func<object, bool> canExecute = null,
Action<Exception> onException = null,
Func<object, bool>? canExecute = null,
Action<Exception>? onException = null,
bool continueOnCapturedContext = false)
```

```csharp
public AsyncCommand(Func<Task> execute,
Func<object, bool> canExecute = null,
Action<Exception> onException = null,
Func<object, bool>? canExecute = null,
Action<Exception>? onException = null,
bool continueOnCapturedContext = false)
```

Expand Down Expand Up @@ -365,6 +414,110 @@ public class ExampleClass
}
```

### `AsyncValueCommand`

Allows for `ValueTask` to safely be used asynchronously with `ICommand`.

If you're new to ValueTask, check out this great write-up, [Understanding the Whys, Whats, and Whens of ValueTask
](https://blogs.msdn.microsoft.com/dotnet/2018/11/07/understanding-the-whys-whats-and-whens-of-valuetask?WT.mc_id=asyncawaitbestpractices-github-bramin).

- `AsyncValueCommand<T> : IAsyncValueCommand<T>`
- `IAsyncValueCommand<T> : ICommand`
- `AsyncValueCommand : IAsyncValueCommand`
- `IAsyncValueCommand : ICommand`

```csharp
public AsyncValueCommand(Func<T, ValueTask> execute,
Func<object, bool>? canExecute = null,
Action<Exception>? onException = null,
bool continueOnCapturedContext = false)
```

```csharp
public AsyncValueCommand(Func<ValueTask> execute,
Func<object, bool>? canExecute = null,
Action<Exception>? onException = null,
bool continueOnCapturedContext = false)
```

```csharp
public class ExampleClass
{
bool _isBusy;

public ExampleClass()
{
ExampleValueTaskCommand = new AsyncValueCommand(ExampleValueTaskMethod);
ExampleValueTaskIntCommand = new AsyncValueCommand<int>(ExampleValueTaskMethodWithIntParameter);
ExampleValueTaskExceptionCommand = new AsyncValueCommand(ExampleValueTaskMethodWithException, onException: ex => Debug.WriteLine(ex.ToString()));
ExampleValueTaskCommandWithCanExecuteChanged = new AsyncValueCommand(ExampleValueTaskMethod, _ => !IsBusy);
ExampleValueTaskCommandReturningToTheCallingThread = new AsyncValueCommand(ExampleValueTaskMethod, continueOnCapturedContext: true);
}

public IAsyncValueCommand ExampleValueTaskCommand { get; }
public IAsyncValueCommand<int> ExampleValueTaskIntCommand { get; }
public IAsyncValueCommand ExampleValueTaskExceptionCommand { get; }
public IAsyncValueCommand ExampleValueTaskCommandWithCanExecuteChanged { get; }
public IAsyncValueCommand ExampleValueTaskCommandReturningToTheCallingThread { get; }

public bool IsBusy
{
get => _isBusy;
set
{
if (_isBusy != value)
{
_isBusy = value;
ExampleValueTaskCommandWithCanExecuteChanged.RaiseCanExecuteChanged();
}
}
}

async ValueTask ExampleValueTaskMethod()
{
var random = new Random();
if (random.Next(10) > 9)
await Task.Delay(1000);
}

async ValueTask ExampleValueTaskMethodWithIntParameter(int parameter)
{
var random = new Random();
if (random.Next(10) > 9)
await Task.Delay(parameter);
}

async ValueTask ExampleValueTaskMethodWithException()
{
var random = new Random();
if (random.Next(10) > 9)
await Task.Delay(1000);

throw new Exception();
}

void ExecuteCommands()
{
_isBusy = true;

try
{
ExampleValueTaskCommand.Execute(null);
ExampleValueTaskIntCommand.Execute(1000);
ExampleValueTaskExceptionCommand.Execute(null);
ExampleValueTaskCommandReturningToTheCallingThread.Execute(null);

if (ExampleValueTaskCommandWithCanExecuteChanged.CanExecute(null))
ExampleValueTaskCommandWithCanExecuteChanged.Execute(null);
}
finally
{
_isBusy = false;
}
}
}
```

## Learn More
- [Removing Async Void](https://johnthiriet.com/removing-async-void/)
- [MVVM Going Async with Async Command](https://johnthiriet.com/mvvm-going-async-with-async-command/)
Expand Down
36 changes: 24 additions & 12 deletions Src/AsyncAwaitBestPractices.MVVM.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,45 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata minClientVersion="2.5">
<id>AsyncAwaitBestPractices.MVVM</id>
<version>4.0.0</version>
<version>4.0.1</version>
<title>Async Extensions for ICommand</title>
<authors>Brandon Minnick, John Thiriet</authors>
<owners>Brandon Minnick</owners>
<license type="expression">MIT</license>
<projectUrl>https://github.com/brminnick/AsyncAwaitBestPractices</projectUrl>
<!-- <iconUrl>TBD</iconUrl> -->
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Includes AsyncCommand and IAsyncCommand which allows ICommand to safely be used asynchronously with Task.</description>
<summary>Includes AsyncCommand and IAsyncCommand which allows ICommand to safely be used asynchronously with Task.</summary>
<tags>task,fire and forget, threading, extensions, system.threading.tasks,async,await</tags>
<description>
Async Extensions for ICommand

Includes AsyncCommand and IAsyncCommand which allows ICommand to safely be used asynchronously with Task.
Includes AsyncValueCommand and IAsyncValueCommand which allows ICommand to safely be used asynchronously with ValueTask
</description>
<summary>
Async Extensions for ICommand

Includes AsyncCommand and IAsyncCommand which allows ICommand to safely be used asynchronously with Task.
Includes AsyncValueCommand and IAsyncValueCommand which allows ICommand to safely be used asynchronously with ValueTask.
</summary>
<tags>task, valuetask, fire and forget, threading, extensions, system.threading.tasks, async, await</tags>
<dependencies>
<dependency id="AsyncAwaitBestPractices" version="4.0.0" />
<dependency id="AsyncAwaitBestPractices" version="4.0.1" />
</dependencies>
<releaseNotes>
New In This Release:
- Added `SafeFireAndForgetExtensions.RemoveDefaultExceptionHandling`
- Fixed InvalidHandleEventException bug when using the incorrect overload for `WeakEventManager&lt;T&gt;.HandleEvent`
- Fixed `NullReferenceException` when calling `ICommand.Execute`
- Improved .NET Standard Dependency to .NET Standard 1.0
- Added Support for ValueTask
- Added `IAsyncValueCommand : ICommand`
- Added `AsyncValueCommand : IAsyncValueCommand`
- Added `IAsyncValueCommand&lt;T&gt; : ICommand`
- Added `AsyncValueCommand&lt;T&gt; : IAsyncValueCommand&lt;T&gt;`
- Added `RaiseCanExecuteChanged` to `IAsyncCommand` and `IAsyncValueCommand`
</releaseNotes>
<repository type="git" url="https://github.com/brminnick/AsyncAwaitBestPractices.git" branch="master" commit="f3d4139c98477bc46e4f4b386b9cd8bdd116142d" />
<copyright>Copyright (c) 2018 Brandon Minnick</copyright>
</metadata>
<files>
<file src="AsyncAwaitBestPractices.MVVM\bin\Release\netstandard1.0\AsyncAwaitBestPractices.MVVM.pdb" target="lib\netstandard1.0\AsyncAwaitBestPractices.MVVM.pdb" />
<file src="AsyncAwaitBestPractices.MVVM\bin\Release\netstandard1.0\AsyncAwaitBestPractices.MVVM.dll" target="lib\netstandard1.0\AsyncAwaitBestPractices.MVVM.dll" />
<file src="AsyncAwaitBestPractices.MVVM\bin\Release\netstandard1.0\AsyncAwaitBestPractices.MVVM.xml" target="lib\netstandard1.0\AsyncAwaitBestPractices.MVVM.xml" />
<file src="AsyncAwaitBestPractices.MVVM\bin\Release\netstandard1.0\AsyncAwaitBestPractices.MVVM.pdb" target="lib\netstandard1.0\AsyncAwaitBestPractices.MVVM.pdb" />
<file src="AsyncAwaitBestPractices.MVVM\bin\Release\netstandard1.0\AsyncAwaitBestPractices.MVVM.dll" target="lib\netstandard1.0\AsyncAwaitBestPractices.MVVM.dll" />
<file src="AsyncAwaitBestPractices.MVVM\bin\Release\netstandard1.0\AsyncAwaitBestPractices.MVVM.xml" target="lib\netstandard1.0\AsyncAwaitBestPractices.MVVM.xml" />
</files>
</package>
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@
<TargetFramework>netstandard1.0</TargetFramework>
<LangVersion>8.0</LangVersion>
<Nullable>enable</Nullable>
<NullableContextOptions>enable</NullableContextOptions>
<ProduceReferenceAssembly>True</ProduceReferenceAssembly>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>portable</DebugType>
<DocumentationFile>bin\Release\netstandard2.0\AsyncAwaitBestPractices.MVVM.xml</DocumentationFile>
<DebugSymbols>true</DebugSymbols>
<DebugType>portable</DebugType>
<DocumentationFile>bin\Release\netstandard2.0\AsyncAwaitBestPractices.MVVM.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\AsyncAwaitBestPractices\AsyncAwaitBestPractices.csproj" />
Expand Down
Loading

0 comments on commit 561e937

Please sign in to comment.