.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 шт):

Автор решения: Alexander Petrov

Вот так код работает:

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, хостится не на форме, а в трее и, насколько я понимаю, не использует поток сообщений формы напрямую. Поэтому его поведение отличается от стандартных оконных контролов.

→ Ссылка
Автор решения: Pavel Mayorov

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);
→ Ссылка