Understand the difference between:
- sequential execution
- raw
Thread TaskTask.WhenAll
public interface IActionRunner
{
Task RunAsync(
IReadOnlyCollection<Func<CancellationToken, Task>> actions,
CancellationToken cancellationToken);
}Open:
src/MultithreadingLab.Core/ActionRunners/SequentialActionRunner.cs
This version should execute one action after another.
Open:
src/MultithreadingLab.Core/ActionRunners/ThreadActionRunner.cs
Requirements:
- Create one
Threadper action. - Start each thread.
- Join each thread.
- Capture exceptions.
- Throw an
AggregateExceptionif any action fails.
Open:
src/MultithreadingLab.Core/ActionRunners/TaskActionRunner.cs
Requirements:
- Schedule actions using
Task.Run. - Await all using
Task.WhenAll. - Respect cancellation.
- Do not swallow exceptions.
Create tests for:
- empty input
- all actions executed
- exception propagation
- cancellation behavior
Run:
dotnet run -c Release --project benchmarks/MultithreadingLab.Benchmarks --filter *ActionRunner*Compare:
- sequential
- raw thread
- task-based
Pros:
- Full control over thread lifetime.
- Good for understanding low-level behavior.
Cons:
- Expensive to create.
- Poor scalability when creating many threads.
- Easy to misuse with async code.
Pros:
- Better default abstraction.
- Uses ThreadPool for CPU-bound work.
- Composes naturally with async/await.
Cons:
- Can still cause ThreadPool starvation if abused.
Task.Runshould not be used blindly around already-async I/O operations.
Use raw Thread for learning or special low-level scenarios. Use Task, async/await, and higher-level primitives for most application code.