diff --git a/_rules/1835.md b/_rules/1835.md index ca3b94d..e16e55b 100644 --- a/_rules/1835.md +++ b/_rules/1835.md @@ -1,7 +1,7 @@ --- rule_id: 1835 rule_category: performance -title: Beware of `async`/`await` deadlocks in single-threaded environments +title: Beware of `async`/`await` deadlocks in special environments (e.g. WPF) severity: 1 --- Consider the following asynchronous method: @@ -12,13 +12,12 @@ Consider the following asynchronous method: return result.ToString(); } -Now when an ASP.NET MVC controller action does this: +Now when a button event handler is implemented like this: - public ActionResult ActionAsync() + public async void Button1_Click(object sender, RoutedEventArgs e) { var data = GetDataAsync().Result; - - return View(data); + textBox1.Text = data; } -You end up with a deadlock. Why? Because the `Result` property getter will block until the `async` operation has completed, but since an `async` method _could_ automatically marshal the result back to the original thread (depending on the current `SynchronizationContext` or `TaskScheduler`) and ASP.NET uses a single-threaded synchronization context, they'll be waiting on each other. A similar problem can also happen on UWP, WPF or a Windows Store C#/XAML app. Read more about this [here](http://blogs.msdn.com/b/pfxteam/archive/2011/01/13/10115163.aspx). +You will likely end up with a deadlock. Why? Because the `Result` property getter will block until the `async` operation has completed, but since an `async` method _could_ automatically marshal the result back to the original thread (depending on the current `SynchronizationContext` or `TaskScheduler`) and WPF uses a single-threaded synchronization context, they'll be waiting on each other. A similar problem can also happen on UWP, WinForms, classical ASP.NET (not ASP.NET Core) or a Windows Store C#/XAML app. Read more about this [here](http://blogs.msdn.com/b/pfxteam/archive/2011/01/13/10115163.aspx). diff --git a/_rules/1840.md b/_rules/1840.md new file mode 100644 index 0000000..3e44fad --- /dev/null +++ b/_rules/1840.md @@ -0,0 +1,18 @@ +--- +rule_id: 1840 +rule_category: performance +title: Await `ValueTask` and `ValueTask` directly and exactly once +severity: 1 +--- +The consumption of the newer and performance related `ValueTask` and `ValueTask` types is more restrictive than consuming `Task` or `Task`. Starting with .NET Core 2.1 the `ValueTask` is not only able to wrap the result `T` or a `Task`, with this version it is also possible to wrap a `IValueTaskSource` / `IValueTaskSource` which gives the developer extra support for reuse and pooling. This enhanced support might lead to unwanted side-effects, as the ValueTask-returning developer might reuse the underlying object after it got awaited. The safest way to consume a `ValueTask` / `ValueTask` is to directly `await` it once, or call `.AsTask()` to get a `Task` / `Task` to overcome these limitations. + + // OK / GOOD + int bytesRead = await stream.ReadAsync(buffer, cancellationToken); + + // OK / GOOD + int bytesRead = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); + + // OK / GOOD - Get task if you want to overcome the limitations exposed by ValueTask / ValueTask + Task task = stream.ReadAsync(buffer, cancellationToken).AsTask(); + +Other usage patterns might still work (like saving the `ValueTask` / `ValueTask` into a variable and awaiting later), but may lead to misuse eventually. Not awaiting a `ValueTask` / `ValueTask` may also cause unwanted side-effects. Read more about `ValueTask` / `ValueTask` and the correct usage [here](https://devblogs.microsoft.com/dotnet/understanding-the-whys-whats-and-whens-of-valuetask/).