Marcin Jahn | Dev Notebook
  • Home
  • Programming
  • Technologies
  • Projects
  • About
  • Home
  • Programming
  • Technologies
  • Projects
  • About
  • An icon of the Core section Core
    • Programs Execution
    • Stack and Heap
    • Asynchronous Programming
      • Overview
      • Event Queues
      • Fibers
      • Stackless Coroutines
  • An icon of the .NET section .NET
    • HTTPClient
    • Async
      • How Async Works
      • TAP Tips
    • Equality
    • Comparisons
    • Enumerables
    • Unit Tests
    • Generic Host
    • Logging
    • Configuration
    • Records
    • Nullability
    • Garbage Collector
    • IL and Allocations
    • gRPC
    • Source Generators
    • Platform Invoke
    • ASP.NET Core
      • Overview
      • Middleware
      • Razor Pages
      • Routing in Razor Pages
      • Web APIs
      • Filters
      • Identity
      • Validation
      • Tips
    • Entity Framework Core
      • Overview
      • Testing
      • Tips
  • An icon of the Angular section Angular
    • Overview
    • Components
    • Directives
    • Services and DI
    • Routing
    • Observables (RxJS)
    • Forms
    • Pipes
    • HTTP
    • Modules
    • NgRx
    • Angular Universal
    • Tips
    • Standalone Components
  • An icon of the JavaScript section JavaScript
    • OOP
    • JavaScript - The Weird Parts
    • JS Functions
    • ES Modules
    • Node.js
    • Axios Tips
    • TypeScript
      • TypeScript Environment Setup
      • TypeScript Tips
    • React
      • React Routing
      • MobX
    • Advanced Vue.js Features
  • An icon of the Rust section Rust
    • Overview
    • Cargo
    • Basics
    • Ownership
    • Structures
    • Enums
    • Organization
    • Collections
    • Error Handling
    • Generics
    • Traits
    • Lifetimes
    • Closures
    • Raw Pointers
    • Smart Pointers
    • Concurrency
    • Testing
    • Tips
  • An icon of the C/C++ section C/C++
    • Compilation
    • Structures
    • OOP in C
    • Pointers
    • Strings
    • Dynamic Memory
    • argc and argv Visualization
  • An icon of the GTK section GTK
    • Overview
    • GObject
    • GJS
  • An icon of the CSS section CSS
    • Responsive Design
    • CSS Tips
    • CSS Pixel
  • An icon of the Unity section Unity
    • Unity
  • An icon of the Functional Programming section Functional Programming
    • Fundamentals of Functional Programming
    • .NET Functional Features
    • Signatures
    • Function Composition
    • Error Handling
    • Partial Application
    • Modularity
    • Category Theory
      • Overview
      • Monoid
      • Other Magmas
      • Functors
  • An icon of the Algorithms section Algorithms
    • Big O Notation
    • Array
    • Linked List
    • Queue
    • Hash Table and Set
    • Tree
    • Sorting
    • Searching
  • An icon of the Architecture section Architecture
    • What is architecture?
    • Domain-Driven Design
    • ASP.NET Core Projects
  • An icon of the Core section Core
    • Programs Execution
    • Stack and Heap
    • Asynchronous Programming
      • Overview
      • Event Queues
      • Fibers
      • Stackless Coroutines
  • An icon of the .NET section .NET
    • HTTPClient
    • Async
      • How Async Works
      • TAP Tips
    • Equality
    • Comparisons
    • Enumerables
    • Unit Tests
    • Generic Host
    • Logging
    • Configuration
    • Records
    • Nullability
    • Garbage Collector
    • IL and Allocations
    • gRPC
    • Source Generators
    • Platform Invoke
    • ASP.NET Core
      • Overview
      • Middleware
      • Razor Pages
      • Routing in Razor Pages
      • Web APIs
      • Filters
      • Identity
      • Validation
      • Tips
    • Entity Framework Core
      • Overview
      • Testing
      • Tips
  • An icon of the Angular section Angular
    • Overview
    • Components
    • Directives
    • Services and DI
    • Routing
    • Observables (RxJS)
    • Forms
    • Pipes
    • HTTP
    • Modules
    • NgRx
    • Angular Universal
    • Tips
    • Standalone Components
  • An icon of the JavaScript section JavaScript
    • OOP
    • JavaScript - The Weird Parts
    • JS Functions
    • ES Modules
    • Node.js
    • Axios Tips
    • TypeScript
      • TypeScript Environment Setup
      • TypeScript Tips
    • React
      • React Routing
      • MobX
    • Advanced Vue.js Features
  • An icon of the Rust section Rust
    • Overview
    • Cargo
    • Basics
    • Ownership
    • Structures
    • Enums
    • Organization
    • Collections
    • Error Handling
    • Generics
    • Traits
    • Lifetimes
    • Closures
    • Raw Pointers
    • Smart Pointers
    • Concurrency
    • Testing
    • Tips
  • An icon of the C/C++ section C/C++
    • Compilation
    • Structures
    • OOP in C
    • Pointers
    • Strings
    • Dynamic Memory
    • argc and argv Visualization
  • An icon of the GTK section GTK
    • Overview
    • GObject
    • GJS
  • An icon of the CSS section CSS
    • Responsive Design
    • CSS Tips
    • CSS Pixel
  • An icon of the Unity section Unity
    • Unity
  • An icon of the Functional Programming section Functional Programming
    • Fundamentals of Functional Programming
    • .NET Functional Features
    • Signatures
    • Function Composition
    • Error Handling
    • Partial Application
    • Modularity
    • Category Theory
      • Overview
      • Monoid
      • Other Magmas
      • Functors
  • An icon of the Algorithms section Algorithms
    • Big O Notation
    • Array
    • Linked List
    • Queue
    • Hash Table and Set
    • Tree
    • Sorting
    • Searching
  • An icon of the Architecture section Architecture
    • What is architecture?
    • Domain-Driven Design
    • ASP.NET Core Projects

TAP Tips

TAP stands for Task-based Asynchronous Pattern. Here’re a few tips how to use it effectively in some scenarios.

Long running operations

Don’t use Task.Run for long running tasks. Task.Factory.StartNew has an option TaskCreationOptions.LongRunning that under the covers creates a new thread and returns a Task that represents the execution. Using this properly requires several non-obvious parameters to be passed in to get the right behavior on all platforms.

Don’t use TaskCreationOptions.LongRunning with async code as this will create a new thread which will be destroyed after first await

public void StartProcessing()
{
var thread = new Thread(ProcessQueue)
{
// This is important as it allows the process to exit while this thread is running
IsBackground = true
};
thread.Start();
}

TaskCompletionSource

Always create TaskCompletionSource<T> with TaskCreationOptions.RunContinuationsAsynchronously. By default, Task continuations will run inline on the same thread that calls Try/Set(Result/Exception/Canceled). As a library author, this means having to understand that calling code can resume directly on your thread. This is extremely dangerous and can result in deadlocks, thread-pool starvation, corruption of state (if code runs unexpectedly) and more.

var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);

CancellationTokenSource

Always dispose CancellationTokenSource(s) used for timeouts

using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))

Cancelling operations

Using CancellationTokens

Good implementation should dispose CancellationTokenRegistration.

public static async Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
// This disposes the registration as soon as one of the tasks trigger
using (cancellationToken.Register(state =>
{
((TaskCompletionSource<object>)state).TrySetResult(null);
},
tcs))
{
var resultTask = await Task.WhenAny(task, tcs.Task);
if (resultTask == tcs.Task)
{
// Operation cancelled
throw new OperationCanceledException(cancellationToken);
}
return await task;
}
}

Using a timeout

Good implementation cancels the timer if operation successfully completes.

public static async Task<T> TimeoutAfter<T>(this Task<T> task, TimeSpan timeout)
{
using (var cts = new CancellationTokenSource())
{
var delayTask = Task.Delay(timeout, cts.Token);
var resultTask = await Task.WhenAny(task, delayTask);
if (resultTask == delayTask)
{
// Operation cancelled
throw new OperationCanceledException();
}
else
{
// Cancel the timer task so that it does not fire
cts.Cancel();
}
return await task;
}
}

Streams

Always call FlushAsync() on StreamWriter or Stream before calling Dispose()

using (var streamWriter = new StreamWriter(context.Response.Body))
{
await streamWriter.WriteAsync("Hello World");
// Force an asynchronous flush
await streamWriter.FlushAsync();
}

Async Eliding

In some cases, we might be able to write methods that return a Task, without any await keywords. Such approach comes with some benefts and drawbacks.

Pros of eliding async

public Task<int> DoSomethingAsync()
{
return InternalAsync();
}
  • (minimal) performance gain - without async - await, the state machine does not need to be generated. An application will execute faster abd it will allocate less memory.

Pros of NOT eliding async

public async Task<int> DoSomethingAsync()
{
return await InternalAsync();
}
  • awaited call will throw both synchronous and asynchronous exceptions

    Eliding causes only synchronous exceptions to be caught:

    public Task<int> DoSomethingAsync()
    {
    try
    {
    return InternalAsync();
    }
    catch(Exception)
    {
    // It will catch only synchronous exceptions, anything
    // asynchronous will not be caught here, but rather
    // on the caller of DoSomethingAsync (unless it's not awaited)
    }
    }
  • Asynchronous and synchronous exceptions are normalized to always be asynchronous.

    public async Task<string> FunctionWithoutEliding()
    {
    throw Exception(); // synchronous exception
    return Something();
    }
    public Task<string> FunctionWithElidinging()
    {
    throw Exception(); // synchronous exception
    return await Something();
    }
    var task = FunctionWithoutEliding(); // throws some
    var result = await task; // Exception thrown here
    var task = FunctionWithElidinging(); // Exception thrown here
    var result = await task;
  • Diagnostics of asynchronous methods is easier (full call stack).

  • AsyncLocal works as expected (stephencleary.com)

Attached/Detached tasks

Task.Run uses Task.Factory.StartNew() under the hood with an option DenyChildAttach. It means that it will ignore children tasks being attached. If we want the attachment, we can use Task.Factory.StartNew:

await Task.Factory.StartNew(() -> {
Task.Factory.StartNew(() -> {
Thread.Sleep(1000);
}, TaskCreationoptions.AttachedToParent);
Task.Factory.StartNew(() -> {
Thread.Sleep(1000);
}, TaskCreationoptions.AttachedToParent);
}, TaskCreationOptions.);

The parent Task will be completed only when its 2 children are finished. Task.Run would complete before that, because it does not allow attachment.

Unwrapping

Task.Run automatically unwraps the result of async operations inside. Task.Factory.StartNew does not do that. Example:

var task = Task.Factory.StartNew(async () => 5);
var result = await await task; //Unwrapping

The way around that is:

var task = Task.Factory.StartNew(async () => 5).Unwrap();
var result = await task; //Already unwrapped
←  How Async Works
© 2023 Marcin Jahn | Dev Notebook | All Rights Reserved. | Built with Astro.