C# Flashcards Preview

Technical Performance / Best Practices > C# > Flashcards

Flashcards in C# Deck (12)
Loading flashcards...
1

You should prefer async Task to async void

To summarize this first guideline, you should prefer async Task to async void. Async Task methods enable easier error-handling, composability and testability. The exception to this guideline is asynchronous event handlers, which must return void. This exception includes methods that are logically event handlers even if they’re not literally event handlers (for example, ICommand.Execute implementations).

2

Async all the way

Async all the way


To summarize this second guideline, you should avoid mixing async and blocking code. Mixed async and blocking code can cause deadlocks, more-complex error handling and unexpected blocking of context threads. The exception to this guideline is the Main method for console applications, or—if you’re an advanced user—managing a partially asynchronous codebase.

Note that console applications don’t cause this deadlock. They have a thread pool SynchronizationContext instead of a one-chunk-at-a-time SynchronizationContext, so when the await completes, it schedules the remainder of the async method on a thread pool thread. The method is able to complete, which completes its returned task, and there’s no deadlock. This difference in behavior can be confusing when programmers write a test console program, observe the partially async code work as expected, and then move the same code into a GUI or ASP.NET application, where it deadlocks.

3

using keyword for unmanaged resouces like database connections, file as it does implement IDispoable

using keyword for unmanaged resouces like database connections, file as it does implement IDispoable

4

it’s usually a bad idea to block on async code by calling Task.Wait or Task.Result.

it’s usually a bad idea to block on async code by calling Task.Wait or Task.Result.

5

TAP Naming conventions

By convention, methods that return commonly awaitable types (e.g. Task, Task, ValueTask, ValueTask) should have names that end with "Async". Methods that start an asynchronous operation but do not return an awaitable type should not have names that end with "Async", but may start with "Begin", "Start", or some other verb to suggest this method does not return or throw the result of the operation.

You can ignore the convention where an event, base class, or interface contract suggests a different name. For example, you shouldn't rename common event handlers, such as Button1_Click.

6

async and await common rules

1:Avoid Using Async Void; instead use async Task whenever possible, where T is the return type of your method.
2:DoSomeStuff(); // synchronous method
await DoSomeLengthyStuffAsync(); // long-running asynchronous method
DoSomeMoreStuff(); // another synchronous method
This allows the compiler to split the calling method at the point of the await keyword. The first part ends with the asynchronous method call; the second part starts with using its result if any, and continues from there on.
3:This allows the compiler to split the calling method at the point of the await keyword. The first part ends with the asynchronous method call; the second part starts with using its result if any, and continues from there on.

4:In order to use the await keyword on a method; its return type must be Task. This allows the compiler to trigger the continuation of our method, once the task completes. In other words, this will work as long as the asynchronous method’s signature is async Task. Had the signature been async void instead, we would have to call it without the await keyword:

DoSomeStuff(); // synchronous method
DoSomeLengthyStuffAsync(); // long-running asynchronous method
DoSomeMoreStuff(); // another synchronous method

5:Execution difference of with wait and without wait for an async method call
DoSomeStuff(); // synchronous method
DoSomeLengthyStuffAsync(); // long-running asynchronous method
DoSomeMoreStuff(); // another synchronous method
The compiler would not complain though. Depending on the side effects of DoSomeLengthyStuffAsync, the code might even work correctly. However, there is one important difference between the two examples. In the first one, DoSomeMoreStuff will only be invoked after DoSomeLengthyStuffAsync completes. In the second one, DoSomeMoreStuff will be invoked immediately after DoSomeLengthyStuffAsync starts. Since in the latter case DoSomeLengthyStuffAsync and DoSomeMoreStuff run in parallel, race conditions might occur. If DoSomeMoreStuff depends on any of DoSomeLengthyStuffAsync’s side effects, these might or might not yet be available when DoSomeMoreStuff wants to use them. Such a bug can be difficult to fix, because it cannot be reproduced reliably. It can also occur only in production environment, where I/O operations are usually slower than in development environment.

6:Always use async task
To avoid such issues altogether, always use async Task as the signature for methods you intend to call from your code. Restrict the usage of async void signature to event handlers, which are not allowed to return anything, and make sure you never call them yourself. If you need to reuse the code in an event handler, refactor it into a separate method returning Task, and call that new method from both the event handler and your method, using await.

7:Beware of Deadlocks
In a way, asynchronous methods behave contagiously. To call an asynchronous method with await, you must make the calling method asynchronous as well, even if it was not async before. Now, all methods calling this newly asynchronous method must also become asynchronous. This pattern repeats itself up the call stack until it finally reaches the entry points, e.g. event handlers.

When one of the methods on this path to the entry points cannot be asynchronous, this poses a problem. For example, constructors. They cannot be asynchronous, therefore you cannot use await in their body. As discussed in the previous section, you could break the asynchronous requirement early by giving a method async void signature, but this prevents you from waiting for its execution to end, which makes it a bad idea in most cases.

Alternatively, you could try synchronously waiting for the asynchronous method to complete, by calling Wait on the returned Task, or reading its Result property. Of course, this synchronous code will temporarily stop your application from processing the message queue, which we wanted to avoid in the first place. Even worse, in some cases you could cause a deadlock in your application with some very innocent looking code:

private async void MyEventHandler(object sender, RoutedEventArgs e)
{
var instance = new InnocentLookingClass();
// further code
}
Any synchronously called asynchronous code in InnocentLookingClass constructor is enough to cause a deadlock:

public class InnocentLookingClass()
{
public InnocentLookingClass()
{
DoSomeLengthyStuffAsync().Wait();
// do some more stuff
}

private async Task DoSomeLengthyStuffAsync()
{
await SomeOtherLengthyStuffAsync();
}

// other class members
}
Let us dissect what is happening in this code.

MyEventHandler synchronously calls InnocentLookingClass constructor, which invokes DoSomeLengthyStuffAsync, which in turn asynchronously invokes SomeOtherLengthyStuffAsync. The execution of the latter method starts; at the same time the main thread blocks at Wait until DoSomeLengthyStuffAsync completes without giving control back to the main message loop.

Eventually SomeOtherLengthyStuffAsync completes and posts a message to the message queue implying that the execution of DoSomeLengthyStuffAsync can continue. Unfortunately, the main thread is waiting for that method to complete instead of processing the messages, and will therefore never trigger it to continue, hence waiting indefinitely.

As you can see, synchronously invoking asynchronous methods can quickly have undesired consequences. Avoid it at all costs; unless you are sure what you are doing, i.e. you are not blocking the main message loop.

7

Avoid blocking calls

ASP.NET Core apps should be designed to process many requests simultaneously. Asynchronous APIs allow a small pool of threads to handle thousands of concurrent requests by not waiting on blocking calls. Rather than waiting on a long-running synchronous task to complete, the thread can work on another request.

A common performance problem in ASP.NET Core apps is blocking calls that could be asynchronous. Many synchronous blocking calls lead to Thread Pool starvation and degraded response times.

Do not:

Block asynchronous execution by calling Task.Wait or Task.Result.

Acquire locks in common code paths. ASP.NET Core apps are most performant when architected to run code in parallel.

Call Task.Run and immediately await it. ASP.NET Core already runs app code on normal Thread Pool threads, so calling Task.Run only results in extra unnecessary Thread Pool scheduling. Even if the scheduled code would block a thread, Task.Run does not prevent that.


Do:

Make hot code paths asynchronous.

Call data access, I/O, and long-running operations APIs asynchronously if an asynchronous API is available. Do not use Task.Run to make a synchronous API asynchronous.

Make controller/Razor Page actions asynchronous. The entire call stack is asynchronous in order to benefit from async/await patterns.

A profiler, such as PerfView, can be used to find threads frequently added to the Thread Pool. The Microsoft-Windows-DotNETRuntime/ThreadPoolWorkerThread/Start event indicates a thread added to the thread pool.

8

do not use Task.Run() to make the call asynchronous

Call data access, I/O, and long-running operations APIs asynchronously if an asynchronous API is available. Do not use Task.Run to make a synchronous API asynchronous.

9

Generalized async return types and ValueTask
to use use value type instead of Reference types

Generalized async return types and ValueTask
Starting with C# 7.0, an async method can return any type that has an accessible GetAwaiter method.

Because Task and Task are reference types, memory allocation in performance-critical paths, particularly when allocations occur in tight loops, can adversely affect performance. Support for generalized return types means that you can return a lightweight value type instead of a reference type to avoid additional memory allocations.

.NET provides the System.Threading.Tasks.ValueTask structure as a lightweight implementation of a generalized task-returning value. To use the System.Threading.Tasks.ValueTask type, you must add the System.Threading.Tasks.Extensions NuGet package to your project. The following example uses the ValueTask structure to retrieve the value of two dice rolls.

using System;
using System.Threading.Tasks;

class Program
{
static Random rnd;

static void Main()
{
Console.WriteLine("Inside the Main method, before calling async method");
Console.WriteLine($"You rolled {GetDiceRoll().Result}");
Console.WriteLine("Inside the Main method, after calling async method");
}

private static async ValueTask GetDiceRoll()
{
Console.WriteLine("...Shaking the dice...");
Console.WriteLine("Inside the GetDiceRoll method, before calling await method");
int roll1 = await Roll();
Console.WriteLine("Inside the GetDiceRoll method, after the first calling to await");
int roll2 = await Roll();
Console.WriteLine("Inside the GetDiceRoll method, after the second calling to await");
return roll1 + roll2;
}

private static async ValueTask Roll()
{
if (rnd == null)
rnd = new Random();
Console.WriteLine("Inside the Roll method, before the calling to await");
await Task.Delay(500);

Console.WriteLine("Inside the Roll method, after the calling to await");
int diceRoll = rnd.Next(1, 7);
return diceRoll;
}
}
// The example displays output like the following:
// ...Shaking the dice...
// You rolled 8

/*
* Inside the Main method, before calling async method
...Shaking the dice...
Inside the GetDiceRoll method, before calling await method
Inside the Roll method, before the calling to await
Inside the Roll method, after the calling to await
Inside the GetDiceRoll method, after the first calling to await
Inside the Roll method, before the calling to await
Inside the Roll method, after the calling to await
Inside the GetDiceRoll method, after the second calling to await
You rolled 10
Inside the Main method, after calling async method
*/

10

Once you go async, all of your callers SHOULD be async

https://github.com/davidfowl/
AspNetCoreDiagnosticScenarios/blob/master/
AsyncGuidance.md

Once you go async, all of your callers SHOULD be async, since efforts to be async amount to nothing unless the entire callstack is async. In many cases, being partially async can be worse than being entirely synchronous. Therefore it is best to go all in, and make everything async at once.

❌ BAD This example uses the Task.Result and as a result blocks the current thread to wait for the result. This is an example of sync over async.

public int DoSomethingAsync()
{
var result = CallDependencyAsync().Result;
return result + 1;
}
✅ GOOD This example uses the await keyword to get the result from CallDependencyAsync.

public async Task DoSomethingAsync()
{
var result = await CallDependencyAsync();
return result + 1;
}

11

Use of async void in ASP.NET Core applications is ALWAYS bad. Avoid it, never do it.

Use of async void in ASP.NET Core applications is ALWAYS bad. Avoid it, never do it. Typically, it's used when developers are trying to implement fire and forget patterns triggered by a controller action. Async void methods will crash the process if an exception is thrown. We'll look at more of the patterns that cause developers to do this in ASP.NET Core applications but here's a simple example:

❌ BAD Async void methods can't be tracked and therefore unhandled exceptions can result in application crashes.

public class MyController : Controller
{
[HttpPost("/start")]
public IActionResult Post()
{
BackgroundOperationAsync();
return Accepted();
}

public async void BackgroundOperationAsync()
{
var result = await CallDependencyAsync();
DoSomething(result);
}
}
✅ GOOD Task-returning methods are better since unhandled exceptions trigger the TaskScheduler.UnobservedTaskException.

public class MyController : Controller
{
[HttpPost("/start")]
public IActionResult Post()
{
Task.Run(BackgroundOperationAsync);
return Accepted();
}

public async Task BackgroundOperationAsync()
{
var result = await CallDependencyAsync();
DoSomething(result);
}
}

12

Prefer Task.FromResult over Task.Run for pre-computed or trivially computed data

ValueTask is even better

For pre-computed results, there's no need to call Task.Run, that will end up queuing a work item to the thread pool that will immediately complete with the pre-computed value. Instead, use Task.FromResult, to create a task wrapping already computed data.

❌ BAD This example wastes a thread-pool thread to return a trivially computed value.

public class MyLibrary
{
public Task AddAsync(int a, int b)
{
return Task.Run(() => a + b);
}
}
✅ GOOD This example uses Task.FromResult to return the trivially computed value. It does not use any extra threads as a result.

public class MyLibrary
{
public Task AddAsync(int a, int b)
{
return Task.FromResult(a + b);
}
}
💡NOTE: Using Task.FromResult will result in a Task allocation. Using ValueTask can completely remove that allocation.

✅ GOOD This example uses a ValueTask to return the trivially computed value. It does not use any extra threads as a result. It also does not allocate an object on the managed heap.

public class MyLibrary
{
public ValueTask AddAsync(int a, int b)
{
return new ValueTask(a + b);
}
}