C# Task в главном потоке и фоновое состояние
Есть пример с AwaitOperator
Который я запускаю под .NET 6.0
Почему при выполнении кода:
Thread.CurrentThread.Name = "MainThread";
Task<int> downloading = DownloadDocsMainPageAsync();
Console.WriteLine($"{Thread.CurrentThread.Name} " +
$"{Thread.CurrentThread.IsBackground} " +
$"{Thread.CurrentThread.ManagedThreadId} main: Launched downloading.");
int bytesLoaded = await downloading;
Console.WriteLine($"{Thread.CurrentThread.Name} " +
$"{Thread.CurrentThread.IsBackground} " +
$"{Thread.CurrentThread.ManagedThreadId} main: Downloaded {bytesLoaded} bytes.");
static async Task<int> DownloadDocsMainPageAsync()
{
Console.WriteLine($"{Thread.CurrentThread.Name} " +
$"{Thread.CurrentThread.IsBackground} " +
$"{Thread.CurrentThread.ManagedThreadId} " +
$"{nameof(DownloadDocsMainPageAsync)}: About to start downloading.");
var client = new HttpClient();
byte[] content = await client.GetByteArrayAsync("https://docs.microsoft.com/en-us/");
Console.WriteLine($"{Thread.CurrentThread.Name} " +
$"{Thread.CurrentThread.IsBackground} " +
$"{Thread.CurrentThread.ManagedThreadId} " +
$"{nameof(DownloadDocsMainPageAsync)}: Finished downloading.");
return content.Length;
}
Я получаю результат:
MainThread False 1 DownloadDocsMainPageAsync: About to start downloading.
MainThread False 1 main: Launched downloading.
True 8 DownloadDocsMainPageAsync: Finished downloading.
True 8 main: Downloaded 132518 bytes.
Который мне сообщает, что:
- Из главного потока я перешёл в
DownloadDocsMainPageAsync - И в
DownloadDocsMainPageAsyncя всё ещё нахожусь главным потоком и делаю асинхронностьGetByteArrayAsyncно окончание результата я уже получаю в другом фоновом потоке из пула потоков - В метод
mainя возвращаюсь тем же фоновым потоком из пула потоков.
Вопросы:
- Куда делся мой главный поток? Почему работа программы продолжается в потоке из пула потоков?
- Почему работа программы продолжается в фоновом потоке, а не в нормальном, который изначальном инициировал Task?
ОБНОВЛЕНИЕ: немного упростил
Но если сделать так
Thread.CurrentThread.Name = "MainThread";
Task t = Task.Factory.StartNew(() =>Work1());
Console.WriteLine(new
{
Thread.CurrentThread.Name,
Thread.CurrentThread.IsBackground,
Thread.CurrentThread.ManagedThreadId
});
SynchronizationContext sc = SynchronizationContext.Current;
await t;
sc.Send((o) =>
{
Console.WriteLine(new
{
Thread.CurrentThread.Name,
Thread.CurrentThread.IsBackground,
Thread.CurrentThread.ManagedThreadId
});
},null);
Console.WriteLine(new
{
Thread.CurrentThread.Name,
Thread.CurrentThread.IsBackground,
Thread.CurrentThread.ManagedThreadId
});
static void Work1()
{
Thread.Sleep(1000);
Console.WriteLine(new
{
Thread.CurrentThread.Name,
Thread.CurrentThread.IsBackground,
Thread.CurrentThread.ManagedThreadId
});
}
Чтобы попытаться якобы попасть в SynchronizationContext, но он оказывается NULL в консольном приложении.
Вообще, это страшно или не страшно, что после асинхронности поток продолжается из фонового потока? Из моих знаний, фоновые потоки - это потоки не имеющие приоритета и в случае завершения программы, они не будут дожидаться окончания.
Если первый пример запустить отладкой и посмотреть на потоки, то MainThread описан как Приложение перешло в состояние останова, но отсутствует код для вывода на экран, так как все потоки исполняли внешний код (как правило, системный код или код платформы).. Это же плохо для штатной дальнейшей работы если я не собираюсь завершать программу, а дальше будут из потока из пула потоков строить вызовы?!
Ответы (1 шт):
Потому что так работает оператор await: продолжение асинхронного метода вызывается в том же потоке, который завершил задачу, если только нет дополнительный указаний, вроде флага RunContinuationsAsynchronously или установленного контекста синхронизации.
Вы начали сетевой запрос в потоке 1, но завершился он в потоке 8 (это нормально для асинхронного кода!). Поскольку контекста синхронизации установлено не было - в том же потоке 8 были вызваны продолжения сначала DownloadDocsMainPageAsync, а потом Main.
То, что у вас работа продолжается в фоновых потоках - совершенно нормально. Если вы опасаетесь что программа завершится раньше времени - то она не завершится пока не завершится задача, которую вы вернули из асинхронного Main. Там на самом деле компилятор сгенерировал блокирующее ожидание, что-то вроде вот такого кода:
private static void $GeneratedMain() => Main().GetAwaiter().GetResult();
Вот вызов GetResult - и есть тот самый внешний код, который исполняется в потоке MainThread.