Threading
Task Parallel Library
C#
.NET
Asynchronous Programming

Creating threads - new Thread vs Task.Factory.StartNew

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

new Thread and Task.Factory.StartNew can both run work concurrently, but they solve different problems. Thread creates a dedicated thread that you manage directly, while StartNew schedules a task, usually on the thread pool, and gives you a composable Task object for coordination, cancellation, and error handling.

Use new Thread When You Need a Real Dedicated Thread

Thread is the lower-level primitive. You decide when it starts, whether it is a background thread, and which thread-specific settings it should use.

csharp
1using System;
2using System.Threading;
3
4var thread = new Thread(() =>
5{
6    Console.WriteLine($"Running on thread {Environment.CurrentManagedThreadId}");
7    Thread.Sleep(500);
8});
9
10thread.IsBackground = true;
11thread.Start();
12thread.Join();

This is appropriate when you truly need thread-level control, such as apartment state, a long-lived message loop, or a dedicated worker that should not compete with short thread-pool tasks.

Use Task.Factory.StartNew for Scheduled Work Units

Task.Factory.StartNew is part of the Task Parallel Library. It represents work, not a guaranteed new thread. By default it uses a scheduler, and in most server or console scenarios that means thread-pool execution.

csharp
1using System;
2using System.Threading;
3using System.Threading.Tasks;
4
5var cts = new CancellationTokenSource();
6
7Task<int> task = Task.Factory.StartNew(
8    () => 21 * 2,
9    cts.Token,
10    TaskCreationOptions.None,
11    TaskScheduler.Default
12);
13
14Console.WriteLine(await task);

This approach is usually better when you want a result, want to await completion, or want to compose multiple operations together.

Know Why Task.Run Is Often the Better Default

For simple fire-and-forget CPU work or an easy await, modern .NET code usually prefers Task.Run. StartNew is more configurable, but that flexibility also makes it easier to misuse. One example is accidentally scheduling onto TaskScheduler.Current instead of the default scheduler.

Another common trap is passing an async delegate to StartNew. That creates a nested task and often requires Unwrap(), while Task.Run handles this more naturally.

Pick Based on Resource Semantics, Not on Syntax

If you need one long-lived thread, Thread makes sense. If you need a logical unit of work that should integrate with await, exception propagation, continuations, or cancellation tokens, a Task is the better abstraction.

The difference matters operationally. Creating too many raw threads increases overhead and reduces scalability. Forcing everything through the thread pool can also be wrong if the job is long-running and should not starve pooled workers.

Remember Scheduling and Lifetime Options

The most important operational distinction is that Task lets you describe work in a scheduler-friendly way, while Thread makes you own the thread directly. If you truly have long-running CPU work, TaskCreationOptions.LongRunning can be a better fit than flooding the shared pool. If you are on a UI stack, task schedulers and await also give you much cleaner control over where continuation code runs.

Common Pitfalls

  • Assuming Task.Factory.StartNew always creates a brand new thread when it usually schedules onto the thread pool instead.
  • Using new Thread for short-lived background work that would be simpler and cheaper as a Task.
  • Omitting the scheduler argument in StartNew and then being surprised by TaskScheduler.Current behavior in more complex environments.
  • Passing an async lambda to StartNew without handling the nested Task correctly.
  • Treating threading as the answer to I/O-bound work when asynchronous I/O APIs would avoid blocking threads entirely.

Summary

  • 'new Thread gives direct control over a dedicated thread.'
  • 'Task.Factory.StartNew schedules work and returns a composable Task.'
  • Most ordinary background work is better expressed as a Task than as a raw thread.
  • 'Task.Run is usually the safer default unless you need StartNew options explicitly.'
  • Choose based on control requirements, lifetime, and scheduling behavior, not just on which API looks shorter.

Course illustration
Course illustration

All Rights Reserved.