Skip to content

Commit

Permalink
Task / ValueTask related guidance (#230)
Browse files Browse the repository at this point in the history
  • Loading branch information
czimpi authored Jan 31, 2022
1 parent 0328fbe commit 4ad2ebe
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 6 deletions.
11 changes: 5 additions & 6 deletions _rules/1835.md
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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).
18 changes: 18 additions & 0 deletions _rules/1840.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
rule_id: 1840
rule_category: performance
title: Await `ValueTask` and `ValueTask<T>` directly and exactly once
severity: 1
---
The consumption of the newer and performance related `ValueTask` and `ValueTask<T>` types is more restrictive than consuming `Task` or `Task<T>`. Starting with .NET Core 2.1 the `ValueTask<T>` is not only able to wrap the result `T` or a `Task<T>`, with this version it is also possible to wrap a `IValueTaskSource` / `IValueTaskSource<T>` 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<T>` is to directly `await` it once, or call `.AsTask()` to get a `Task` / `Task<T>` 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<T>
Task<int> task = stream.ReadAsync(buffer, cancellationToken).AsTask();

Other usage patterns might still work (like saving the `ValueTask` / `ValueTask<T>` into a variable and awaiting later), but may lead to misuse eventually. Not awaiting a `ValueTask` / `ValueTask<T>` may also cause unwanted side-effects. Read more about `ValueTask` / `ValueTask<T>` and the correct usage [here](https://devblogs.microsoft.com/dotnet/understanding-the-whys-whats-and-whens-of-valuetask/).

0 comments on commit 4ad2ebe

Please sign in to comment.