This is a series of articles about Multi-threading.
- Multi-Threading (1), Concept: What, Why
- Multi-Threading (2), Implementation Overview
- Multi-Threading (2-1), Different MultiThreading Topics --- This article
- Multi-Threading (3), async, await in C#
- Multi-Threading (4), delegate based Asynchronous Programming Model
- Multi-Threading (5), Summary
A - Introduction
The first article in this series, Multi-Threading (1), Concept: What, Why, written on 01/30/2023, introduced the multi-threading concept. The following articles should be the implementations of different approaches.
The content of the article:
- A - Introduction
- B - Async/Await - Best Practices in Asynchronous Programming
- C - Difference between Thread, ThreadPool & BackgroundWorker
- D - Task vs Thread differences
- E - Threads vs. Tasks
- F - Options for Asynchronous Programming
- G - Multithreading: immutability
- H - MS Threading Model:
- I - Asynchronous pattern:
Note
Writing a summary article is somewhat quite difficult. Before one jumps into each topic, it is hard to have an overview; on the other hand, if writing a series of articles, when one makes them done, it seems not necessary to write a summary article.
This article on MultiThreading overview is actually my study notes recorded over decades. In the first half, I tried to make them logical, as written in the article, Multi-Threading (2), Implementation Overview, while in the second half of the article, I just simply list the topics there, written in this article. For myself, it is still a learning process; for others, one may see how others learn multi-threading.
B - Async/Await - Best Practices in Asynchronous Programming
- Avoid async void Prefer async Task methods over async void methods
- There are three possible return types for async methods: Task, Task<T>, and void, but the natural return types for async methods are just Task and Task<T>. When converting from synchronous to asynchronous code, any method returning a type T becomes an async method returning Task<T>, and any method returning void becomes an async method returning Task.
- Void-returning async methods have a specific purpose: to make asynchronous event handlers possible.
- lAsync void methods have different error-handling semantics.
- Async void methods have different composing semantics.
- Async void methods are difficult to test.
- Async void methods have several disadvantages compared to async Task methods, but they’re quite useful in one particular case: asynchronous event handlers.
- async/await - when to return a Task vs void? - Stack Overflow
- 1) Normally, you would want to return a Task. The main exception should be when you need to have a void return type (for events). If there's no reason to disallow having the caller await your task, why disallow it?
- 2) async methods that return void are special in another aspect: they represent top-level async operations and have additional rules that come into play when your task returns an exception. The easiest way is to show the difference is with an example:
- An async method in C# can have any one of these signatures.
- 1. async Task MyMethod() { } which creates a method that can be awaited but does not return any value.
- 2. async Task<T> MyReturningMethod { return default(T); } which creates a method that can be awaited and returns a value of the type T.
- 3. async void MyFireAndForgetMethod() { }, which allows for fire and forget methods and cannot be awaited.
- We should always avoid having a void return type in async methods.
- async void methods exist for the single purpose of being used as an event handler. More specifically, it has been introduced to support UI elements event handlers, nothing else.
C - Difference between Thread, ThreadPool & BackgroundWorker
- I do not understand what is the difference between calling any method with the help of a thread class or ThreadPool class
- The main difference is whether you manage the thread lifetime yourself (Thread) or you take advantage of the "pool" of threads the framework already creates.
- Using ThreadPool.QueueUserWorkItem will (often) use a thread that already exists in the system. This means you don't have the overhead of spinning up a new thread - instead, a set of threads is already there, and your work is just run on one of them. This can be especially beneficial if you're doing many operations which are short-lived.
- When you use a new Thread, you actually spin up a new thread that will live until your delegate completes its execution.
- Note that, as of .NET 4 and 4.5, I'd recommend using the Task and Task<T> types instead of making your own threads or using ThreadPool.QueueUserWorkItem. These (by default) use the thread pool to execute but provide many other useful abstractions, especially with C# 5 and the await/async keywords.
- Now also tell me what is BackgroundWorker class and how it is different from Thread & ThreadPool class.
- The BackgroundWorker class is an abstraction over the thread pool. It uses the ThreadPool to queue up "work" (your DoWork event handler) but also provides extra functionality which allows progress and completion events to be posted back to the initial SynchronizationContext (which, in a GUI program, will typically be the user interface thread). This simplifies tasks where you want to update a UI for progress or completion notifications as background "work" is running on a background thread.
D - Task vs Thread differences
Thread is a lower-level concept: if you're directly starting a thread, you know it will be a separate thread rather than executing on the thread pool, etc.
The task is more than just an abstraction of "where to run some code" though - it's really just "the promise of a result in the future". So as some different examples:
- Task.Delay doesn't need any actual CPU time; it's just like setting a timer to go off in the future
- A task returned by WebClient.DownloadStringTaskAsync won't take much CPU time locally; it's representing a result that is likely to spend most of its time in network latency or remote work (at the web server)
- A task returned by Task.Run() really is saying, "I want you to execute this code separately"; the exact thread on which that code executes depends on a number of factors.
Note that the Task<T> abstraction is pivotal to the async support in C# 5.
In general, I'd recommend that you use the higher level abstraction wherever you can: in modern C# code, you should rarely need to explicitly start your own thread.
E - Threads vs. Tasks
.Net has three low-level mechanisms to run code in parallel: Thread, ThreadPool, and Task. These three mechanism serve different purposes.

Join the conversation! Your thoughts help the community grow.