Skip to content

Commit e768a96

Browse files
Merge pull request #19 from brminnick/Release-v3.0.0
Release v3.0.0
2 parents 351ac97 + f662705 commit e768a96

27 files changed

+1020
-122
lines changed

README.md

Lines changed: 105 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ Inspired by [John Thiriet](https://github.com/johnthiriet)'s blog posts: [Removi
1212

1313
Available on NuGet: https://www.nuget.org/packages/AsyncAwaitBestPractices/
1414

15-
- `SafeFireAndForget`
16-
- An extension method to safely fire-and-forget a `Task`:
17-
- `WeakEventManager`
15+
- `SafeFireAndForget`
16+
- An extension method to safely fire-and-forget a `Task`
17+
- Ensures the `Task` will rethrow an `Exception` if an `Exception` is caught in `IAsyncStateMachine.MoveNext()`
18+
- `WeakEventManager`
1819
- Avoids memory leaks when events are not unsubscribed
1920
- Used by `AsyncCommand` and `AsyncCommand<T>`
20-
- [Usage instructions](#asyncawaitbestpractices-3)
21+
- [Usage instructions](#asyncawaitbestpractices-3)
2122

2223
### AsyncAwaitBestPractices.MVVM
2324

@@ -113,15 +114,53 @@ Never, never, never, never, never use `.Result` or `.Wait()`:
113114
An extension method to safely fire-and-forget a `Task`:
114115

115116
```csharp
116-
public static async void SafeFireAndForget(this System.Threading.Tasks.Task task, bool continueOnCapturedContext = true, System.Action<System.Exception> onException = null)
117+
public static async void SafeFireAndForget(this System.Threading.Tasks.Task task, bool continueOnCapturedContext = false, System.Action<System.Exception> onException = null)
117118
```
118119

120+
#### Basic Usage
121+
122+
```csharp
123+
void HandleButtonTapped(object sender, EventArgs e)
124+
{
125+
// Allows the async Task method to safely run on a different thread while not awaiting its completion
126+
// onException: If an Exception is thrown, print it to the Console
127+
ExampleAsyncMethod().SafeFireAndForget(onException: ex => Console.WriteLine(ex));
128+
129+
// HandleButtonTapped continues execution here while `ExampleAsyncMethod()` is running on a different thread
130+
// ...
131+
}
132+
133+
async Task ExampleAsyncMethod()
134+
{
135+
await Task.Delay(1000);
136+
}
137+
```
138+
139+
#### Advanced Usage
140+
119141
```csharp
142+
void InitializeSafeFireAndForget()
143+
{
144+
// Initialize SafeFireAndForget
145+
// 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.
146+
SafeFireAndForgetExtensions.Initialize(shouldAlwaysRethrowException: false);
147+
148+
// SafeFireAndForget will print every exception to the Console
149+
SafeFireAndForgetExtensions.SetDefaultExceptionHandling(ex => Console.WriteLine(ex));
150+
}
151+
120152
void HandleButtonTapped(object sender, EventArgs e)
121153
{
122154
// Allows the async Task method to safely run on a different thread while not awaiting its completion
123-
// If an exception is thrown, Console.WriteLine
124-
ExampleAsyncMethod().SafeFireAndForget(onException: ex => Console.WriteLine(ex.ToString()));
155+
156+
// 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`
157+
158+
// Because we set `SetDefaultExceptionHandling` in `void InitializeSafeFireAndForget()`, the entire exception will also be printed to the Console
159+
ExampleAsyncMethod().SafeFireAndForget<WebException>(onException: ex =>
160+
{
161+
if(e.Response is HttpWebResponse webResponse)
162+
Console.WriteLine($"Status Code: {webResponse.StatusCode}");
163+
});
125164

126165
// HandleButtonTapped continues execution here while `ExampleAsyncMethod()` is running on a different thread
127166
// ...
@@ -130,26 +169,62 @@ void HandleButtonTapped(object sender, EventArgs e)
130169
async Task ExampleAsyncMethod()
131170
{
132171
await Task.Delay(1000);
172+
throw new WebException();
133173
}
134174
```
135175

136176
### `WeakEventManager`
137177

138-
An event implementation that enables the [garbage collector to collect an object without needing to unsubscribe event handlers](http://paulstovell.com/blog/weakevents), inspired by [Xamarin.Forms.WeakEventManager](https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Core/WeakEventManager.cs):
178+
An event implementation that enables the [garbage collector to collect an object without needing to unsubscribe event handlers](http://paulstovell.com/blog/weakevents).
179+
180+
Inspired by [Xamarin.Forms.WeakEventManager](https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Core/WeakEventManager.cs).
181+
182+
#### Using `EventHandler`
139183

140184
```csharp
141-
readonly WeakEventManager _weakEventManager = new WeakEventManager();
185+
readonly WeakEventManager _canExecuteChangedEventManager = new WeakEventManager();
142186

143187
public event EventHandler CanExecuteChanged
144188
{
145189
add => _weakEventManager.AddEventHandler(value);
146190
remove => _weakEventManager.RemoveEventHandler(value);
147191
}
148192

149-
public void RaiseCanExecuteChanged() => _weakEventManager.HandleEvent(this, EventArgs.Empty, nameof(CanExecuteChanged));
193+
public void OnCanExecuteChanged() => _canExecuteChangedEventManager.HandleEvent(this, EventArgs.Empty, nameof(CanExecuteChanged));
194+
```
195+
196+
#### Using `Delegate`
197+
198+
```csharp
199+
readonly WeakEventManager _propertyChangedEventManager = new WeakEventManager();
200+
201+
public event PropertyChangedEventHandler PropertyChanged
202+
{
203+
add => _propertyChangedEventManager.AddEventHandler(value);
204+
remove => _propertyChangedEventManager.RemoveEventHandler(value);
205+
}
206+
207+
public void OnPropertyChanged([CallerMemberName]string propertyName = "") => _propertyChangedEventManager.HandleEvent(this, new PropertyChangedEventArgs(propertyName), nameof(PropertyChanged));
208+
```
209+
210+
#### Using `Action`
211+
212+
```csharp
213+
readonly WeakEventManager _weakActionEventManager = new WeakEventManager();
214+
215+
public event Action ActionEvent
216+
{
217+
add => _weakActionEventManager.AddEventHandler(value);
218+
remove => _weakActionEventManager.RemoveEventHandler(value);
219+
}
220+
221+
public void OnActionEvent(string message) => _weakActionEventManager.HandleEvent(message, nameof(ActionEvent));
150222
```
151223

152224
### `WeakEventManager<T>`
225+
An event implementation that enables the [garbage collector to collect an object without needing to unsubscribe event handlers](http://paulstovell.com/blog/weakevents), inspired by [Xamarin.Forms.WeakEventManager](https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Core/WeakEventManager.cs).
226+
227+
#### Using `EventHandler<T>`
153228

154229
```csharp
155230
readonly WeakEventManager<string> _errorOcurredEventManager = new WeakEventManager<string>();
@@ -160,7 +235,21 @@ public event EventHandler<string> ErrorOcurred
160235
remove => _errorOcurredEventManager.RemoveEventHandler(value);
161236
}
162237

163-
public void RaiseErrorOcurred(string message) => _weakEventManager.HandleEvent(this, message, nameof(ErrorOcurred));
238+
public void OnErrorOcurred(string message) => _errorOcurredEventManager.HandleEvent(this, message, nameof(ErrorOcurred));
239+
```
240+
241+
#### Using `Action<T>`
242+
243+
```csharp
244+
readonly WeakEventManager<string> _weakActionEventManager = new WeakEventManager<string>();
245+
246+
public event Action<string> ActionEvent
247+
{
248+
add => _weakActionEventManager.AddEventHandler(value);
249+
remove => _weakActionEventManager.RemoveEventHandler(value);
250+
}
251+
252+
public void OnActionEvent(string message) => _weakActionEventManager.HandleEvent(message, nameof(ActionEvent));
164253
```
165254

166255
## AsyncAwaitBestPractices.MVVM
@@ -178,14 +267,14 @@ Allows for `Task` to safely be used asynchronously with `ICommand`:
178267
public AsyncCommand(Func<T, Task> execute,
179268
Func<object, bool> canExecute = null,
180269
Action<Exception> onException = null,
181-
bool continueOnCapturedContext = true)
270+
bool continueOnCapturedContext = false)
182271
```
183272

184273
```csharp
185274
public AsyncCommand(Func<Task> execute,
186275
Func<object, bool> canExecute = null,
187276
Action<Exception> onException = null,
188-
bool continueOnCapturedContext = true)
277+
bool continueOnCapturedContext = false)
189278
```
190279

191280
```csharp
@@ -196,13 +285,13 @@ public class ExampleClass
196285
ExampleAsyncCommand = new AsyncCommand(ExampleAsyncMethod);
197286
ExampleAsyncIntCommand = new AsyncCommand<int>(ExampleAsyncMethodWithIntParameter);
198287
ExampleAsyncExceptionCommand = new AsyncCommand(ExampleAsyncMethodWithException, onException: ex => Console.WriteLine(ex.ToString()));
199-
ExampleAsyncCommandNotReturningToTheCallingThread = new AsyncCommand(ExampleAsyncMethod, continueOnCapturedContext: false);
288+
ExampleAsyncCommandReturningToTheCallingThread = new AsyncCommand(ExampleAsyncMethod, continueOnCapturedContext: true);
200289
}
201290

202291
public IAsyncCommand ExampleAsyncCommand { get; }
203292
public IAsyncCommand<int> ExampleAsyncIntCommand { get; }
204293
public IAsyncCommand ExampleAsyncExceptionCommand { get; }
205-
public IAsyncCommand ExampleAsyncCommandNotReturningToTheCallingThread { get; }
294+
public IAsyncCommand ExampleAsyncCommandReturningToTheCallingThread { get; }
206295

207296
async Task ExampleAsyncMethod()
208297
{
@@ -225,7 +314,7 @@ public class ExampleClass
225314
ExampleAsyncCommand.Execute(null);
226315
ExampleAsyncIntCommand.Execute(1000);
227316
ExampleAsyncExceptionCommand.Execute(null);
228-
ExampleAsyncCommandNotReturningToTheCallingThread.Execute(null);
317+
ExampleAsyncCommandReturningToTheCallingThread.Execute(null);
229318
}
230319
}
231320
```

Src/AsyncAwaitBestPractices.MVVM.nuspec

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,34 @@
22
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
33
<metadata minClientVersion="2.5">
44
<id>AsyncAwaitBestPractices.MVVM</id>
5-
<version>2.1.1</version>
5+
<version>3.0.0</version>
66
<title>Task Extensions for MVVM</title>
77
<authors>Brandon Minnick, John Thiriet</authors>
88
<owners>Brandon Minnick</owners>
9-
<licenseUrl>https://github.com/brminnick/AsyncAwaitBestPractices/blob/master/LICENSE.md</licenseUrl>
9+
<license type="expression">MIT</license>
1010
<projectUrl>https://github.com/brminnick/AsyncAwaitBestPractices</projectUrl>
1111
<!-- <iconUrl>TBD</iconUrl> -->
1212
<requireLicenseAcceptance>false</requireLicenseAcceptance>
1313
<description>Includes AsyncCommand and IAsyncCommand which allows ICommand to safely be used asynchronously with Task.</description>
1414
<summary>Includes AsyncCommand and IAsyncCommand which allows ICommand to safely be used asynchronously with Task.</summary>
1515
<tags>task,fire and forget, threading, extensions, system.threading.tasks,async,await</tags>
1616
<dependencies>
17-
<dependency id="AsyncAwaitBestPractices" version="2.1.1" />
17+
<dependency id="AsyncAwaitBestPractices" version="3.0.0" />
1818
</dependencies>
1919
<releaseNotes>
2020
New In This Release:
21-
- Performance improvements to SafeFireAndForget
21+
- Added support for `event Action` and `event Action&lt;T&gt;`
22+
- Added support for `.SafeFireAndForget&lt;TException&gt;()`
23+
- Added `SafeFireAndForgetExtensions.SetDefaultExceptionHandling(Action&lt;Exception&gt; onException)` to set a default action for every call to `SafeFireAndForget`
24+
- Added `SafeFireAndForgetExtensions.Initialize(bool shouldAlwaysRethrowException = false)`. When set to `true` will rethrow every exception caught by `SafeFireAndForget`. Warning: `SafeFireAndForgetExtensions.Initialize(true)` is only recommended for DEBUG environments.
25+
- Added support for `Exception innerException` to `InvalidCommandParameterException`
26+
- Breaking Change: Changed default value to `continueOnCapturedContext = false`. This improves performance by not requiring a context switch when `.SafeFireAndForget()` and `IAsyncCommand` have completed.
2227
</releaseNotes>
2328
<copyright>Copyright (c) 2018 Brandon Minnick</copyright>
2429
</metadata>
2530
<files>
26-
<file src="AsyncAwaitBestPractices.MVVM\bin\Release\netstandard2.0\AsyncAwaitBestPractices.MVVM.pdb" target="lib\netstandard\AsyncAwaitBestPractices.MVVM.pdb" />
27-
<file src="AsyncAwaitBestPractices.MVVM\bin\Release\netstandard2.0\AsyncAwaitBestPractices.MVVM.dll" target="lib\netstandard\AsyncAwaitBestPractices.MVVM.dll" />
28-
<file src="AsyncAwaitBestPractices.MVVM\bin\Release\netstandard2.0\AsyncAwaitBestPractices.MVVM.xml" target="lib\netstandard\AsyncAwaitBestPractices.MVVM.xml" />
31+
<file src="AsyncAwaitBestPractices.MVVM\bin\Release\netstandard2.0\AsyncAwaitBestPractices.MVVM.pdb" target="lib\netstandard2.0\AsyncAwaitBestPractices.MVVM.pdb" />
32+
<file src="AsyncAwaitBestPractices.MVVM\bin\Release\netstandard2.0\AsyncAwaitBestPractices.MVVM.dll" target="lib\netstandard2.0\AsyncAwaitBestPractices.MVVM.dll" />
33+
<file src="AsyncAwaitBestPractices.MVVM\bin\Release\netstandard2.0\AsyncAwaitBestPractices.MVVM.xml" target="lib\netstandard2.0\AsyncAwaitBestPractices.MVVM.xml" />
2934
</files>
3035
</package>

Src/AsyncAwaitBestPractices.MVVM/AsyncAwaitBestPractices.MVVM.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
<PropertyGroup>
44
<TargetFramework>netstandard2.0</TargetFramework>
55
<LangVersion>latest</LangVersion>
6+
<ProduceReferenceAssembly>True</ProduceReferenceAssembly>
67
</PropertyGroup>
78
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
89
<DebugSymbols>true</DebugSymbols>
9-
<DebugType>full</DebugType>
10+
<DebugType>portable</DebugType>
1011
<DocumentationFile>bin\Release\netstandard2.0\AsyncAwaitBestPractices.MVVM.xml</DocumentationFile>
1112
</PropertyGroup>
1213
<ItemGroup>

Src/AsyncAwaitBestPractices.MVVM/AsyncCommand.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public sealed class AsyncCommand<T> : IAsyncCommand<T>
2828
public AsyncCommand(Func<T, Task> execute,
2929
Func<object, bool> canExecute = null,
3030
Action<Exception> onException = null,
31-
bool continueOnCapturedContext = true)
31+
bool continueOnCapturedContext = false)
3232
{
3333
_execute = execute ?? throw new ArgumentNullException(nameof(execute), $"{nameof(execute)} cannot be null");
3434
_canExecute = canExecute ?? (_ => true);
@@ -104,7 +104,7 @@ public sealed class AsyncCommand : IAsyncCommand
104104
public AsyncCommand(Func<Task> execute,
105105
Func<object, bool> canExecute = null,
106106
Action<Exception> onException = null,
107-
bool continueOnCapturedContext = true)
107+
bool continueOnCapturedContext = false)
108108
{
109109
_execute = execute ?? throw new ArgumentNullException(nameof(execute), $"{nameof(execute)} cannot be null");
110110
_canExecute = canExecute ?? (_ => true);

Src/AsyncAwaitBestPractices.MVVM/InvalidCommandParameterException.cs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,35 @@ namespace AsyncAwaitBestPractices.MVVM
55
/// <summary>
66
/// Represents errors that occur during IAsyncCommand execution.
77
/// </summary>
8-
public class InvalidCommandParameterException : Exception
8+
class InvalidCommandParameterException : Exception
99
{
1010
/// <summary>
1111
/// Initializes a new instance of the <see cref="T:TaskExtensions.MVVM.InvalidCommandParameterException"/> class.
1212
/// </summary>
1313
/// <param name="excpectedType">Excpected parameter type for AsyncCommand.Execute.</param>
1414
/// <param name="actualType">Actual parameter type for AsyncCommand.Execute.</param>
15-
public InvalidCommandParameterException(Type excpectedType, Type actualType) : base(CreateErrorMessage(excpectedType, actualType))
15+
/// <param name="innerException">Inner Exception</param>
16+
public InvalidCommandParameterException(Type excpectedType, Type actualType, Exception innerException) : this(CreateErrorMessage(excpectedType, actualType), innerException)
17+
{
18+
19+
}
20+
21+
/// <summary>
22+
/// Initializes a new instance of the <see cref="T:TaskExtensions.MVVM.InvalidCommandParameterException"/> class.
23+
/// </summary>
24+
/// <param name="excpectedType">Excpected parameter type for AsyncCommand.Execute.</param>
25+
/// <param name="actualType">Actual parameter type for AsyncCommand.Execute.</param>
26+
public InvalidCommandParameterException(Type excpectedType, Type actualType) : this(CreateErrorMessage(excpectedType, actualType))
27+
{
28+
29+
}
30+
31+
/// <summary>
32+
/// Initializes a new instance of the <see cref="T:TaskExtensions.MVVM.InvalidCommandParameterException"/> class.
33+
/// </summary>
34+
/// <param name="message">Exception Message</param>
35+
/// <param name="innerException">Inner Exception</param>
36+
public InvalidCommandParameterException(string message, Exception innerException) : base(message, innerException)
1637
{
1738

1839
}

Src/AsyncAwaitBestPractices.UnitTests/AsyncAwaitBestPractices.UnitTests.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44
<TargetFramework>netcoreapp2.1</TargetFramework>
55
<LangVersion>latest</LangVersion>
66
<IsPackable>false</IsPackable>
7+
<ProduceReferenceAssembly>True</ProduceReferenceAssembly>
78
</PropertyGroup>
89
<ItemGroup>
910
<PackageReference Include="nunit" Version="3.12.0" />
1011
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
11-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.1.1" />
12+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
1213
</ItemGroup>
1314
<ItemGroup>
1415
<ProjectReference Include="..\AsyncAwaitBestPractices.MVVM\AsyncAwaitBestPractices.MVVM.csproj" />

Src/AsyncAwaitBestPractices.UnitTests/BaseTest.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,23 +29,24 @@ protected event EventHandler<string> TestStringEvent
2929
protected Task NoParameterTask() => Task.Delay(Delay);
3030
protected Task IntParameterTask(int delay) => Task.Delay(delay);
3131
protected Task StringParameterTask(string text) => Task.Delay(Delay);
32-
protected Task NoParameterImmediateExceptionTask() => throw new Exception();
33-
protected Task ParameterImmediateExceptionTask(int delay) => throw new Exception();
32+
protected Task NoParameterImmediateNullReferenceExceptionTask() => throw new NullReferenceException();
33+
protected Task ParameterImmediateNullReferenceExceptionTask(int delay) => throw new NullReferenceException();
3434

35-
protected async Task NoParameterDelayedExceptionTask()
35+
protected async Task NoParameterDelayedNullReferenceExceptionTask()
3636
{
3737
await Task.Delay(Delay);
38-
throw new Exception();
38+
throw new NullReferenceException();
3939
}
4040

41-
protected async Task IntParameterDelayedExceptionTask(int delay)
41+
protected async Task IntParameterDelayedNullReferenceExceptionTask(int delay)
4242
{
4343
await Task.Delay(delay);
44-
throw new Exception();
44+
throw new NullReferenceException();
4545
}
4646

4747
protected bool CanExecuteTrue(object parameter) => true;
4848
protected bool CanExecuteFalse(object parameter) => false;
49+
protected bool CanExecuteDynamic(object booleanParameter) => (bool)booleanParameter;
4950
#endregion
5051
}
5152
}

0 commit comments

Comments
 (0)