Skip to content

C# Tips & Tricks

FullstackCodingGuy edited this page Feb 23, 2024 · 8 revisions

Async Programming

Task vs Thread

Threads execute Tasks which are scheduled by a TaskScheduler.

Task represents some work that needs to be done. A Task may or may not be completed. The moment when it completes can be right now or in the future.

Tasks have nothing to do with Threads and this is the cause of many misconceptions, Task is not thread. Task does not guarantee parallel execution. Task does not belong to a Thread or anything like that. They are two separate concepts and should be treated as such.

If the Task is completed and not faulted then the continuation task will be scheduled. Faulted state means that there was an exception. Tasks have an associated TaskScheduler which is used to schedule a continuation Task, or any other child Tasks that are required by the current Task.

Threads just as in any OS represent execution of code. Threads keep track what you execute and where you execute. Threads have a call stack, store local variables, and the address of the currently executing instruction. In C# each thread also has an associated SynchronizationContext which is used to communicate between different types of threads

Task.Yield

When you use async/await, there is no guarantee that the method you call when you do await FooAsync() will actually run asynchronously. The internal implementation is free to return using a completely synchronous path.

If you're making an API where it's critical that you don't block and you run some code asynchronously, and there's a chance that the called method will run synchronously (effectively blocking), using await Task.Yield() will force your method to be asynchronous, and return control at that point. The rest of the code will execute at a later time (at which point, it still may run synchronously) on the current context.

This can also be useful if you make an asynchronous method that requires some "long running" initialization, ie:

private async void button_Click(object sender, EventArgs e) { await Task.Yield(); // Make us async right away

  var data = ExecuteFooOnUIThread(); // This will run on the UI thread at some point later

  await UseDataAsync(data);

}

Without the Task.Yield() call, the method will execute synchronously all the way up to the first call to await.

Don't Block on Async Code

Avoiding Deadlocks

image

The code example should be obvious what it does if you are at least a bit familiar with async/await. The request is done asynchronously and the thread is free to work on other tasks while the server responds. This is the ideal case.

Another way to perform

image

Everything you do with async and await end up in an execution queue. Each Task is queued up using a TaskScheduler which can do anything it wants with your Task. This is where things get interesting, the TaskScheduler depends on context you are currently in.

References

Clone this wiki locally