Skip to content

Commit 561e937

Browse files
2 parents 23c7693 + f3d4139 commit 561e937

36 files changed

+1060
-118
lines changed

.gitignore

100644100755
File mode changed.

README.md

Lines changed: 162 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ Inspired by [John Thiriet](https://github.com/johnthiriet)'s blog posts: [Removi
1313
Available on NuGet: https://www.nuget.org/packages/AsyncAwaitBestPractices/
1414

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

2323
### AsyncAwaitBestPractices.MVVM
@@ -31,6 +31,12 @@ Available on NuGet: https://www.nuget.org/packages/AsyncAwaitBestPractices/
3131
- `AsyncCommand : IAsyncCommand`
3232
- `IAsyncCommand<T> : ICommand`
3333
- `AsyncCommand<T> : IAsyncCommand<T>`
34+
35+
- Allows for `ValueTask` to safely be used asynchronously with `ICommand`:
36+
- `IAsyncValueCommand : ICommand`
37+
- `AsyncValueCommand : IAsyncValueCommand`
38+
- `IAsyncValueCommand<T> : ICommand`
39+
- `AsyncValueCommand<T> : IAsyncValueCommand<T>`
3440
- [Usage instructions](#asyncawaitbestpracticesmvvm-2)
3541

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

132-
#### Basic Usage
138+
```csharp
139+
public static async void SafeFireAndForget(this System.Threading.Tasks.ValueTask task, bool continueOnCapturedContext = false, System.Action<System.Exception> onException = null)
140+
```
141+
142+
#### Basic Usage - Task
133143

134144
```csharp
135145
void HandleButtonTapped(object sender, EventArgs e)
@@ -148,6 +158,30 @@ async Task ExampleAsyncMethod()
148158
}
149159
```
150160

161+
#### Basic Usage - ValueTask
162+
163+
If you're new to ValueTask, check out this great write-up, [Understanding the Whys, Whats, and Whens of ValueTask
164+
](https://blogs.msdn.microsoft.com/dotnet/2018/11/07/understanding-the-whys-whats-and-whens-of-valuetask?WT.mc_id=asyncawaitbestpractices-github-bramin).
165+
166+
```csharp
167+
void HandleButtonTapped(object sender, EventArgs e)
168+
{
169+
// Allows the async ValueTask method to safely run on a different thread while the calling thread continues, not awaiting its completion
170+
// onException: If an Exception is thrown, print it to the Console
171+
ExampleValueTaskMethod().SafeFireAndForget(onException: ex => Console.WriteLine(ex));
172+
173+
// HandleButtonTapped continues execution here while `ExampleAsyncMethod()` is running on a different thread
174+
// ...
175+
}
176+
177+
async ValueTask ExampleValueTaskMethod()
178+
{
179+
var random = new Random();
180+
if (random.Next(10) > 9)
181+
await Task.Delay(1000);
182+
}
183+
```
184+
151185
#### Advanced Usage
152186

153187
```csharp
@@ -175,17 +209,32 @@ void HandleButtonTapped(object sender, EventArgs e)
175209
ExampleAsyncMethod().SafeFireAndForget<WebException>(onException: ex =>
176210
{
177211
if(ex.Response is HttpWebResponse webResponse)
178-
Console.WriteLine($"Status Code: {webResponse.StatusCode}");
212+
Console.WriteLine($"Task Exception\n Status Code: {webResponse.StatusCode}");
213+
});
214+
215+
ExampleValueTaskMethod().SafeFireAndForget<WebException>(onException: ex =>
216+
{
217+
if(ex.Response is HttpWebResponse webResponse)
218+
Console.WriteLine($"ValueTask Error\n Status Code: {webResponse.StatusCode}");
179219
});
180220

181-
// HandleButtonTapped continues execution here while `ExampleAsyncMethod()` is running on a different thread
221+
// HandleButtonTapped continues execution here while `ExampleAsyncMethod()` and `ExampleValueTaskMethod()` run in the background
182222
}
183223

184224
async Task ExampleAsyncMethod()
185225
{
186226
await Task.Delay(1000);
187227
throw new WebException();
188228
}
229+
230+
async ValueTask ExampleValueTaskMethod()
231+
{
232+
var random = new Random();
233+
if (random.Next(10) > 9)
234+
await Task.Delay(1000);
235+
236+
throw new WebException();
237+
}
189238
```
190239

191240
### `WeakEventManager`
@@ -282,15 +331,15 @@ Allows for `Task` to safely be used asynchronously with `ICommand`:
282331

283332
```csharp
284333
public AsyncCommand(Func<T, Task> execute,
285-
Func<object, bool> canExecute = null,
286-
Action<Exception> onException = null,
334+
Func<object, bool>? canExecute = null,
335+
Action<Exception>? onException = null,
287336
bool continueOnCapturedContext = false)
288337
```
289338

290339
```csharp
291340
public AsyncCommand(Func<Task> execute,
292-
Func<object, bool> canExecute = null,
293-
Action<Exception> onException = null,
341+
Func<object, bool>? canExecute = null,
342+
Action<Exception>? onException = null,
294343
bool continueOnCapturedContext = false)
295344
```
296345

@@ -365,6 +414,110 @@ public class ExampleClass
365414
}
366415
```
367416

417+
### `AsyncValueCommand`
418+
419+
Allows for `ValueTask` to safely be used asynchronously with `ICommand`.
420+
421+
If you're new to ValueTask, check out this great write-up, [Understanding the Whys, Whats, and Whens of ValueTask
422+
](https://blogs.msdn.microsoft.com/dotnet/2018/11/07/understanding-the-whys-whats-and-whens-of-valuetask?WT.mc_id=asyncawaitbestpractices-github-bramin).
423+
424+
- `AsyncValueCommand<T> : IAsyncValueCommand<T>`
425+
- `IAsyncValueCommand<T> : ICommand`
426+
- `AsyncValueCommand : IAsyncValueCommand`
427+
- `IAsyncValueCommand : ICommand`
428+
429+
```csharp
430+
public AsyncValueCommand(Func<T, ValueTask> execute,
431+
Func<object, bool>? canExecute = null,
432+
Action<Exception>? onException = null,
433+
bool continueOnCapturedContext = false)
434+
```
435+
436+
```csharp
437+
public AsyncValueCommand(Func<ValueTask> execute,
438+
Func<object, bool>? canExecute = null,
439+
Action<Exception>? onException = null,
440+
bool continueOnCapturedContext = false)
441+
```
442+
443+
```csharp
444+
public class ExampleClass
445+
{
446+
bool _isBusy;
447+
448+
public ExampleClass()
449+
{
450+
ExampleValueTaskCommand = new AsyncValueCommand(ExampleValueTaskMethod);
451+
ExampleValueTaskIntCommand = new AsyncValueCommand<int>(ExampleValueTaskMethodWithIntParameter);
452+
ExampleValueTaskExceptionCommand = new AsyncValueCommand(ExampleValueTaskMethodWithException, onException: ex => Debug.WriteLine(ex.ToString()));
453+
ExampleValueTaskCommandWithCanExecuteChanged = new AsyncValueCommand(ExampleValueTaskMethod, _ => !IsBusy);
454+
ExampleValueTaskCommandReturningToTheCallingThread = new AsyncValueCommand(ExampleValueTaskMethod, continueOnCapturedContext: true);
455+
}
456+
457+
public IAsyncValueCommand ExampleValueTaskCommand { get; }
458+
public IAsyncValueCommand<int> ExampleValueTaskIntCommand { get; }
459+
public IAsyncValueCommand ExampleValueTaskExceptionCommand { get; }
460+
public IAsyncValueCommand ExampleValueTaskCommandWithCanExecuteChanged { get; }
461+
public IAsyncValueCommand ExampleValueTaskCommandReturningToTheCallingThread { get; }
462+
463+
public bool IsBusy
464+
{
465+
get => _isBusy;
466+
set
467+
{
468+
if (_isBusy != value)
469+
{
470+
_isBusy = value;
471+
ExampleValueTaskCommandWithCanExecuteChanged.RaiseCanExecuteChanged();
472+
}
473+
}
474+
}
475+
476+
async ValueTask ExampleValueTaskMethod()
477+
{
478+
var random = new Random();
479+
if (random.Next(10) > 9)
480+
await Task.Delay(1000);
481+
}
482+
483+
async ValueTask ExampleValueTaskMethodWithIntParameter(int parameter)
484+
{
485+
var random = new Random();
486+
if (random.Next(10) > 9)
487+
await Task.Delay(parameter);
488+
}
489+
490+
async ValueTask ExampleValueTaskMethodWithException()
491+
{
492+
var random = new Random();
493+
if (random.Next(10) > 9)
494+
await Task.Delay(1000);
495+
496+
throw new Exception();
497+
}
498+
499+
void ExecuteCommands()
500+
{
501+
_isBusy = true;
502+
503+
try
504+
{
505+
ExampleValueTaskCommand.Execute(null);
506+
ExampleValueTaskIntCommand.Execute(1000);
507+
ExampleValueTaskExceptionCommand.Execute(null);
508+
ExampleValueTaskCommandReturningToTheCallingThread.Execute(null);
509+
510+
if (ExampleValueTaskCommandWithCanExecuteChanged.CanExecute(null))
511+
ExampleValueTaskCommandWithCanExecuteChanged.Execute(null);
512+
}
513+
finally
514+
{
515+
_isBusy = false;
516+
}
517+
}
518+
}
519+
```
520+
368521
## Learn More
369522
- [Removing Async Void](https://johnthiriet.com/removing-async-void/)
370523
- [MVVM Going Async with Async Command](https://johnthiriet.com/mvvm-going-async-with-async-command/)

Src/AsyncAwaitBestPractices.MVVM.nuspec

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,45 @@
22
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
33
<metadata minClientVersion="2.5">
44
<id>AsyncAwaitBestPractices.MVVM</id>
5-
<version>4.0.0</version>
5+
<version>4.0.1</version>
66
<title>Async Extensions for ICommand</title>
77
<authors>Brandon Minnick, John Thiriet</authors>
88
<owners>Brandon Minnick</owners>
99
<license type="expression">MIT</license>
1010
<projectUrl>https://github.com/brminnick/AsyncAwaitBestPractices</projectUrl>
1111
<!-- <iconUrl>TBD</iconUrl> -->
1212
<requireLicenseAcceptance>false</requireLicenseAcceptance>
13-
<description>Includes AsyncCommand and IAsyncCommand which allows ICommand to safely be used asynchronously with Task.</description>
14-
<summary>Includes AsyncCommand and IAsyncCommand which allows ICommand to safely be used asynchronously with Task.</summary>
15-
<tags>task,fire and forget, threading, extensions, system.threading.tasks,async,await</tags>
13+
<description>
14+
Async Extensions for ICommand
15+
16+
Includes AsyncCommand and IAsyncCommand which allows ICommand to safely be used asynchronously with Task.
17+
Includes AsyncValueCommand and IAsyncValueCommand which allows ICommand to safely be used asynchronously with ValueTask
18+
</description>
19+
<summary>
20+
Async Extensions for ICommand
21+
22+
Includes AsyncCommand and IAsyncCommand which allows ICommand to safely be used asynchronously with Task.
23+
Includes AsyncValueCommand and IAsyncValueCommand which allows ICommand to safely be used asynchronously with ValueTask.
24+
</summary>
25+
<tags>task, valuetask, fire and forget, threading, extensions, system.threading.tasks, async, await</tags>
1626
<dependencies>
17-
<dependency id="AsyncAwaitBestPractices" version="4.0.0" />
27+
<dependency id="AsyncAwaitBestPractices" version="4.0.1" />
1828
</dependencies>
1929
<releaseNotes>
2030
New In This Release:
21-
- Added `SafeFireAndForgetExtensions.RemoveDefaultExceptionHandling`
22-
- Fixed InvalidHandleEventException bug when using the incorrect overload for `WeakEventManager&lt;T&gt;.HandleEvent`
23-
- Fixed `NullReferenceException` when calling `ICommand.Execute`
24-
- Improved .NET Standard Dependency to .NET Standard 1.0
31+
- Added Support for ValueTask
32+
- Added `IAsyncValueCommand : ICommand`
33+
- Added `AsyncValueCommand : IAsyncValueCommand`
34+
- Added `IAsyncValueCommand&lt;T&gt; : ICommand`
35+
- Added `AsyncValueCommand&lt;T&gt; : IAsyncValueCommand&lt;T&gt;`
36+
- Added `RaiseCanExecuteChanged` to `IAsyncCommand` and `IAsyncValueCommand`
2537
</releaseNotes>
2638
<repository type="git" url="https://github.com/brminnick/AsyncAwaitBestPractices.git" branch="master" commit="f3d4139c98477bc46e4f4b386b9cd8bdd116142d" />
2739
<copyright>Copyright (c) 2018 Brandon Minnick</copyright>
2840
</metadata>
2941
<files>
30-
<file src="AsyncAwaitBestPractices.MVVM\bin\Release\netstandard1.0\AsyncAwaitBestPractices.MVVM.pdb" target="lib\netstandard1.0\AsyncAwaitBestPractices.MVVM.pdb" />
31-
<file src="AsyncAwaitBestPractices.MVVM\bin\Release\netstandard1.0\AsyncAwaitBestPractices.MVVM.dll" target="lib\netstandard1.0\AsyncAwaitBestPractices.MVVM.dll" />
32-
<file src="AsyncAwaitBestPractices.MVVM\bin\Release\netstandard1.0\AsyncAwaitBestPractices.MVVM.xml" target="lib\netstandard1.0\AsyncAwaitBestPractices.MVVM.xml" />
42+
<file src="AsyncAwaitBestPractices.MVVM\bin\Release\netstandard1.0\AsyncAwaitBestPractices.MVVM.pdb" target="lib\netstandard1.0\AsyncAwaitBestPractices.MVVM.pdb" />
43+
<file src="AsyncAwaitBestPractices.MVVM\bin\Release\netstandard1.0\AsyncAwaitBestPractices.MVVM.dll" target="lib\netstandard1.0\AsyncAwaitBestPractices.MVVM.dll" />
44+
<file src="AsyncAwaitBestPractices.MVVM\bin\Release\netstandard1.0\AsyncAwaitBestPractices.MVVM.xml" target="lib\netstandard1.0\AsyncAwaitBestPractices.MVVM.xml" />
3345
</files>
3446
</package>

Src/AsyncAwaitBestPractices.MVVM/AsyncAwaitBestPractices.MVVM.csproj

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,12 @@
44
<TargetFramework>netstandard1.0</TargetFramework>
55
<LangVersion>8.0</LangVersion>
66
<Nullable>enable</Nullable>
7-
<NullableContextOptions>enable</NullableContextOptions>
87
<ProduceReferenceAssembly>True</ProduceReferenceAssembly>
98
</PropertyGroup>
109
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
11-
<DebugSymbols>true</DebugSymbols>
12-
<DebugType>portable</DebugType>
13-
<DocumentationFile>bin\Release\netstandard2.0\AsyncAwaitBestPractices.MVVM.xml</DocumentationFile>
10+
<DebugSymbols>true</DebugSymbols>
11+
<DebugType>portable</DebugType>
12+
<DocumentationFile>bin\Release\netstandard2.0\AsyncAwaitBestPractices.MVVM.xml</DocumentationFile>
1413
</PropertyGroup>
1514
<ItemGroup>
1615
<ProjectReference Include="..\AsyncAwaitBestPractices\AsyncAwaitBestPractices.csproj" />

0 commit comments

Comments
 (0)