C# async/await in depth

01. C# Async/Await/Task Explained (Deep Dive). (Channel “Raw Coding”).
“In this tutorial we conver the asynchronous programming style in C# we take a deep diving looking at the state machine that the async keyword spawns. How the await keyword creates checkpoints in the state machine. And how the Task class allows us to bridge our code to use the asynchronous programming model.”

02. How to use Async/Await/Task in C#. (Channel “Raw Coding”)
“In this tutorial we take a look at how to use async, await and Task in C#. Primarily looking at good practices and how to avoid common pitfalls such as creating unecessary state machines, blocking threads, using ConfigureAwait when making libraries and how to avoid async in constructors.”

Related videos

03. Semaphore Explained C#.
In this Semaphore tutorial, I answer the questions: what is semaphore? what is semaphore used for? how to use semaphore? We go over a quick analogy to understand what the semaphore does and how to use it. We then look at a real world example of how it can be used to solve problems caused by asynchronous programming.

04. C# Channels Explained (System.Threading.Channels).
In this c# channels tutorial I answer the questions: what is a channel? how does a channel work? how to use a channel? when to use a channel? what problem does a channel solve?
Contents:
1. Basic problem that would require shared state to improve.
2. Simple custom implementation of channels.
3. Real World Example of Channels in ASP.NET Core


Links on the Channel subject:
An Introduction to Channels in C#. Jeremy Bytes
Channel Class (Namespace: System.Threading.Channels)
Channel<T> Class (Namespace: System.Threading.Channels)

7 thoughts on “C# async/await in depth”

  1. Asynchronous and synchronous wrappers

    Should I expose asynchronous wrappers for synchronous methods?
    •• “If a developer needs to achieve responsiveness or parallelism with synchronous APIs, they can simply wrap the invocation with a method like Task.Run.”
    Should I expose synchronous wrappers for asynchronous methods?
    •• In the case of async/await, this typically means making sure that any awaits inside of the asynchronous implementation you’re calling are using ConfigureAwait(false) on all await points; this will prevent the await from trying to marshal back to the current SynchronizationContext. As a library implementer, it’s a best practice to always use ConfigureAwait(false) on all of your awaits, unless you have a specific reason not to; this is good not only to help avoid these kinds of deadlock problems, but also for performance, as it avoids unnecessary marshaling costs.
    •• The simplest way to do that is to invoke the asynchronous work from the ThreadPool, such as by wrapping the invocation in a Task.Run, e.g.

    int Sync()
    {
        return Task.Run(() => Library.FooAsync()).Result;
    }
    

    How to safely mix sync and async code? [duplicate]

  2. Task.Run continues on the same thread causing deadlock
    https://stackoverflow.com/questions/46492823/task-run-continues-on-the-same-thread-causing-deadlock

    Task.Wait and “Inlining” (Optimization)
    https://devblogs.microsoft.com/pfxteam/task-wait-and-inlining/

    Why is teaching this so hard? (“Don’t Block on Async Code”)
    https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html#comment-1fd877bd-6ec9-32d4-8c11-e21f10018d21

    Parallel Computing – It’s All About the SynchronizationContext. By Stephen Cleary | February 2011. MSDN Magazine.
    https://learn.microsoft.com/en-us/archive/msdn-magazine/2011/february/msdn-magazine-parallel-computing-it-s-all-about-the-synchronizationcontext

    // Progress Reporting with UI Updates
    
    private void button1_Click(object sender, EventArgs e)
    {
      // This TaskScheduler captures SynchronizationContext.Current.
      TaskScheduler taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
      // Start a new task (this uses the default TaskScheduler, 
      // so it will run on a ThreadPool thread).
      Task.Factory.StartNew(() =>
      {
        // We are running on a ThreadPool thread here.
    
  
        ; // Do some work.
    
    
        // Report progress to the UI.
        Task reportProgressTask = Task.Factory.StartNew(() =>
          {
            // We are running on the UI thread here.
            ; // Update the UI with our progress.
          },
          CancellationToken.None,
          TaskCreationOptions.None,
          taskScheduler
        );
        reportProgressTask.Wait();
      
        ; // Do more work.
      });
    }
    

    How can I get a TaskScheduler for a Dispatcher?
    https://stackoverflow.com/questions/6368885/how-can-i-get-a-taskscheduler-for-a-dispatcher

  3. Simplified implementation of static Task.WaitAsync which is available in .NET 6.

    public static class TaskExtensions
    {
        public static async Task WaitAsync(this Task task)
        {
            // Trick with TaskCompletionSource
            var tcs = new TaskCompletionSource();
    
            // .ConfigureAwait(false) can be also added here.
            // Note, in production ready code exception and cancellation needs to be handled as well.
            task.ContinueWith(t => tcs.SetResult(true));
    
            // asynchronous await, which allows to reuse the thread while waiting
            await tcs.Task;
        }
    }
    

    Note, synchronous Task.Wait is not used, because it blocks the thread even if it is run as lambda:
    await Task.Run(() => task.Wait())

    Task.WhenAll available for similar purposes. But it can’t be used with timeout or cancellation token. While the code above can be extended to support such features.

  4. How Async/Await Really Works in C# – Stephen Toub – MSFT

    1. Iterators are effectively coroutines provided by the C# language / compiler, with the compiler expanding my Fib iterator into a full-blown state machine
    2. Related info: Understanding the Whys, Whats, and Whens of ValueTask – Stephen Toub – MSFT
    3. Analyze memory usage by using the .NET Object Allocation tool
    4. The async lambda is actually an async void method. Async methods return to their caller the moment they hit the first suspension point. If this were an async Task method, that’s when the Task would be returned. But in the case of an async void, nothing is returned.
  5. “The Result property is a blocking property. If you try to access it before its task is finished, the thread that’s currently active is blocked until the task completes and the value is available. In most cases, you should access the value by using await instead of accessing the property directly.”

    Source: Async return types (C#)

Leave a Reply

Your email address will not be published. Required fields are marked *