Does async/await creates new thread?

In short: no.

Longer explanation:

  • task that want to do I/O write call to driver write
  • driver operations are performed asynchronously
  • write operations on devices is done without CPU involved
  • finish of writing is done via interrupt
  • interrupt, schedules Deferred Procedure Call
  • DPC schedules Asynchronous Procedure Call
  • APC notifies thread that I/O write is done
  • task that was doing I/O is scheduled with continuation of its method after I/O is done.
  • some threads might need to be borrowed for handling APC and such but no thread is spawn to handle I/O

I recommend reading this post: https://blog.stephencleary.com/2013/11/there-is-no-thread.html. This will explain the topic much better than I can.

Further read:

https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/task-asynchronous-programming-model#BKMK_Threads

Asynchronous programming in .NET

Asynchronous programming patterns in .NET:

Ways of achieving asynchronous code in .NET

  • async, await
  • Task class API (Run, WhenAll, WhenAny, etc.)
  • ThreadPool.QueueUserWorkItem(ThreadProc);

Further read:

async/await in .NET

https://learn.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/

async/await in C#

async/await syntax should be used for I/O bound asynchronous operations only.

public async Task<int> GetUrlContentLengthAsync()
{
    var client = new HttpClient();

    Task<string> getStringTask =
        client.GetStringAsync("https://docs.microsoft.com/dotnet");

    DoIndependentWork();

    string contents = await getStringTask;

    return contents.Length;
}

Compiler transforms code into a state machine that to track yielding execution for await and resuming execution when a background job is finished.

Things to keep in mind:

  • async method needs await
  • add Async suffix to asynchronous methods
  • async void for ‘Fire and Forget’ work i.e. event Handlers
  • be aware of mixing LINQ with async lambdas
  • wait for tasks in a non-blocking manner (do not use Task.Result i.e.)
  • for tight loops use ValueTasks
  • Consider using ConfigureAwait(false) for performance and for avoiding deadlocks

Further read:

https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/task-asynchronous-programming-model

CPU bound vs I/O bound

There are two types of asynchronous operations in .NET.

  • I/O bound
  • CPU bound

For I/O bound async/await syntax should be used. TPL should not be used for this. async methods does not spawns new thread to hand le I/O work asynchronously.

For CPU bound operations TPL should be used (Task.Run). This spawns new thread. Performance testing should be done to check if overhead of new thread and context switching is not greater than actual work to be done.

  • CPU bound should be done in thread pool (Task.Run or Parallel.ForEach)
  • I/O bound should not
  • UI thread should do minimal work (mostly update UI or react to user actions)
  • extensive computations should be done on different thread (CPU bound).
  • I/O waits should be awaited (less overhead since ThreadPool have some overhead)
  • if I/O task are awaited then it is easier to update UI with new data (only one thread is allowed to update UI – main one)

Further read:

https://learn.microsoft.com/en-us/shows/three-essential-tips-for-async/tip-2-distinguish-cpu-bound-work-from-io-bound-work