C#SeniorTechnical
В чём разница между Task и Thread в C#?
Thread — OS-поток с фиксированным стеком (~1 МБ), дорог в создании. Task — абстракция над пулом потоков ThreadPool, поддерживает async/await, продолжения и отмену. Для I/O-операций используйте Task; Thread — только для долгих CPU-задач с изолированным состоянием.
Task vs Thread в C#
Thread — прямая обёртка над системным потоком OS. Каждый поток стоит ~1 МБ стека и добавляет overhead на переключение контекста. Task — логическая единица работы, которая выполняется планировщиком. Для CPU-работы используется ThreadPool; для I/O — механизм async/await с IOCP (Windows) или epoll (Linux), который не блокирует потоки вовсе.
Thread — прямое создание потока
var thread = new Thread(() =>
{
Console.WriteLine($"Thread ID: {Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(1000); // блокирует поток
});
thread.IsBackground = true; // не мешает завершению приложения
thread.Priority = ThreadPriority.BelowNormal;
thread.Start();
thread.Join(); // ждём завершения
Task — через ThreadPool
// Task.Run — CPU-работа в ThreadPool
var task = Task.Run(() =>
{
Console.WriteLine($"Pool thread: {Thread.CurrentThread.ManagedThreadId}");
return ComputeResult();
});
int result = await task;
// Несколько задач параллельно
var tasks = Enumerable.Range(0, 4).Select(i => Task.Run(() => Process(i)));
await Task.WhenAll(tasks);
async/await — I/O без блокировки потока
// Этот код НЕ блокирует поток ThreadPool во время ожидания сети
public async Task<string> FetchPageAsync(string url)
{
using var http = new HttpClient();
// Во время await поток освобождается для других задач
string content = await http.GetStringAsync(url);
return content;
}
// 1000 параллельных запросов — НЕ создаёт 1000 потоков!
var urls = Enumerable.Range(0, 1000).Select(i => $"https://api.example.com/item/{i}");
var results = await Task.WhenAll(urls.Select(FetchPageAsync));
Когда что использовать
- Task + async/await — I/O-операции (HTTP, файлы, БД), фоновые задачи, параллельная работа. Выбор по умолчанию.
- Task.Run — CPU-интенсивные вычисления, которые нужно вынести из UI/request потока.
- Thread — долгоживущие фоновые потоки с изолированным состоянием (например, поток читает очередь в бесконечном цикле), нужен специфический
ApartmentState(COM/WPF). - Parallel.For / PLINQ — параллельная обработка коллекций с автоматическим разделением на CPU-ядра.
Отмена и исключения
var cts = new CancellationTokenSource();
var task = Task.Run(async () =>
{
await Task.Delay(5000, cts.Token); // поддерживает отмену
return 42;
}, cts.Token);
cts.CancelAfter(1000);
try
{
int result = await task;
}
catch (OperationCanceledException)
{
Console.WriteLine("Задача отменена");
}
catch (Exception ex)
{
// Исключения из Task доступны через await или task.Exception
Console.WriteLine(ex.Message);
}
Подводные камни
Thread.Sleepблокирует поток;await Task.Delay— освобождает его. В async-коде всегда используйтеTask.Delay.- Задачи без
await— fire-and-forget: исключения поглощаются молча. Минимум: логируйте вtask.ContinueWith(t => Log(t.Exception), TaskContinuationOptions.OnlyOnFaulted). Task.ResultиTask.Wait()в async-контексте вызывают deadlock (особенно в ASP.NET Classic сSynchronizationContext) — всегда используйтеawait.- Создавать новый
Threadдля каждого запроса — исчерпывает OS-ресурсы при нагрузке; ThreadPool масштабируется автоматически. async voidметоды нельзя awaited и не дают поймать исключения — используйте только в обработчиках событий.- Неправильный
ConfigureAwait(false)в библиотечном коде внутри ASP.NET — захват контекста без необходимости, лишний overhead.
Common mistakes
- Путать Task как представление асинхронной операции и Thread как ОС-поток с похожим механизмом из другой версии или платформы.
- Игнорировать runtime-границы C#: lifecycle, DI scope, SQL translation, UI thread или platform API.
- Не обсуждать null/empty/error cases и поведение под нагрузкой.
What the interviewer is testing
- Кандидат объясняет Task как представление асинхронной операции и Thread как ОС-поток на конкретном примере, а не только определением.
- Указывает последствия для производительности, тестируемости и поддержки.
- Различает документированное поведение текущего стека и устаревшие практики.