Performance Guidelines
Consider using Any() to determine whether an IEnumerable<T> is empty
(AV1800)
When a member or local function returns an IEnumerable<T> or other collection class that does not expose a Count property, use the Any() extension method rather than Count() to determine whether the collection contains items. If you do use Count(), you risk that iterating over the entire collection might have a significant impact (such as when it really is an IQueryable<T> to a persistent store).
Only use async/await for I/O-bound or long-running activities
(AV1820)
The use of async/await won’t automagically run something on a worker thread as Task.Run does. It just suspends execution at the await point and resumes execution after the task has completed. In other words, use async/await only for I/O-bound operations.
[!IMPORTANT] Exception: Tasks returned from
Task.Run(which starts a background operation in parallel) can eventually be awaited to obtain their results, or passed to a method likeTask.WhenAllthat is awaited.
Prefer Task.Run for CPU-intensive activities
(AV1825)
If you need to execute a CPU-bound operation, use Task.Run to offload the work to a thread from the Thread Pool. Remember that you have to marshal the result back to your main thread manually.
For long-running operations, use Task.Factory.StartNew with TaskCreationOptions.LongRunning to hint the runtime to use a dedicated thread instead of a thread pool thread.
Beware of mixing up async/await with Task.Wait
(AV1830)
await does not block the current thread but simply instructs the compiler to generate a state-machine. However, Task.Wait blocks the thread and may even cause deadlocks (see AV1835).
Beware of async/await deadlocks in UI frameworks (e.g. WPF, WinForms)
(AV1835)
Consider the following asynchronous method:
private async Task<string> GetDataAsync()
{
var result = await MyWebService.GetDataAsync();
return result.ToString();
}
Now when a button event handler is implemented like this:
public async void Button1_Click(object sender, RoutedEventArgs e)
{
var data = GetDataAsync().Result;
textBox1.Text = data;
}
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 WinForms. Read more about this here.
Await ValueTask and ValueTask<T> directly and exactly once
(AV1840)
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.