.NET 6.0 Task.Delay блокирует поток навсегда
static async Task Foo() {
while (true) {
await Task.Delay(1000);
Console.WriteLine("PRINT");
}
}
static void Main(string[] args) {
var strip = new MenuStrip();
Foo();
Console.ReadKey();
}
Данный пример кода демонстрирует странное поведение: после создания экземпляра MenuStrip метод Foo блокируется на моменте вызова Task.Delay. Если не создавать экземпляр MenuStrip, всё работает как должно - каждую секунду на консоль выводится сообщение PRINT. Почему так происходит?
Target Framework: .NET 6.0
Target OS: Windows
Проект: https://cloud.mail.ru/public/kj7m/PQt2DfzXk
Ответы (2 шт):
Вот так код работает:
static async Task Foo()
{
while (true)
{
await Task.Delay(1000).ConfigureAwait(false);
Console.WriteLine("PRINT");
}
}
static void Main(string[] args)
{
var strip = new MenuStrip();
Foo();
Console.ReadKey();
}
Добавлен .ConfigureAwait(false).
Обычное консольное приложение не имеет контекста синхронизации, но данная программа по факту является приложением WinForms, поэтому в ней есть контекст синхронизации. Соответственно, нужно учитывать его наличие.
Атрибут [STAThread], скорее всего, понадобится только в случае использование COM-объектов или компонентов, использующих их внутри себя.
Компонент NotifyIcon, хостится не на форме, а в трее и, насколько я понимаю, не использует поток сообщений формы напрямую. Поэтому его поведение отличается от стандартных оконных контролов.
MenuStrip, являясь компонентом Windows Forms, автоматически устанавливает контекст синхронизации Windows Forms в своём конструкторе.
Избежать этого можно задав свойству WindowsFormsSynchronizationContext.AutoInstall значение false:
WindowsFormsSynchronizationContext.AutoInstall = false;
var strip = new MenuStrip();
Foo();
Console.ReadKey();
Однако, как правило компонентам этот контекст зачем-то нужен, так что в общем случае просто ставить false нежелательно. Вместо этого можно явно вынести вызов метода Foo из контекста синхронизации:
var strip = new MenuStrip();
Task.Run(() => Foo());
Console.ReadKey();
Также, если уж вы создаёте какие-то контролы Windows Forms - скорее всего, вам понадобится главный цикл. Его запуск также устранит проблему:
var strip = new MenuStrip();
Foo();
Application.Run();
Если вам при этом нужно закрытие приложения при нажатии клавиши в консоли - читать придётся в другом потоке, а закрывать - через контекст приложения:
var strip = new MenuStrip();
Foo();
var context = new ApplicationContext();
Task.Run(() => {
Console.WriteLine("Press any key to exit");
Console.ReadKey();
context.ExitThread(); // хотя название тут ExitThread, на самом деле это выход из Application.Run
});
Application.Run(context);