Постановка задачи
У нас есть тесты, которые в какой-то момент вызывают установку SynchronizationContext в текущем потоке nunit. Насколько мне известно, сочетание этого с ожиданием приводит к тупиковой ситуации. Проблема в том, что мы повсюду смешиваем бизнес-логику с проблемами пользовательского интерфейса. Не идеально, но сейчас я ничего не могу изменить.
Пример кода
[Test]
public async Task DeadLock()
{
// force the creation of a SynchronizationContext
var form = new Form1();
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
await Task.Delay(10);
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
}
Этот тест будет мертвым (.NET 4.6.1). Я не знаю, почему. Мое предположение заключалось в том, что поток nunit, который как бы «становится» потоком пользовательского интерфейса, имеет работу в очереди сообщений, которая должна быть очищена до того, как продолжение можно будет запланировать. Итак, только для целей тестирования я вставил вызов
System.Windows.Forms.Application.DoEvents();
прямо перед ожиданием. И вот что странно: тест больше не будет блокироваться, но продолжение выполняется не в предыдущем SynchronizationContext, а в потоке пула потоков (SynchronizationContext.Current == null и другой идентификатор управляемого потока)! Это очевидно? По сути, добавление этого вызова ведет себя как ConfigureAwait (false).
Кто-нибудь знает, почему тестовые тупики?
Предполагая, что это связано с тем, как nunit ожидает завершения асинхронных тестов, я подумал, что запускаю весь тест в отдельном потоке:
[Test]
public void DeadLock2()
{
Task.Run(
async () =>
{
// force the creation of a SynchronizationContext
var form = new Form1();
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
//System.Windows.Forms.Application.DoEvents();
await Task.Delay(10);
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
}).Wait();
}
но это не решает проблемы. «Ожидание» никогда не вернется. Обратите внимание, что я не могу использовать ConfigureAwait (false), поскольку в продолжениях есть код, который должен быть в потоке пользовательского интерфейса (хотя он удаляет мертвую блокировку).