Await и SynchronizationContext в управляемом компоненте, размещенном неуправляемым приложением
[EDITED] Это похоже на ошибку В рамках'с реализациейApplication.DoEventsчто ямы сообщилиВот, Восстановление неверного контекста синхронизации в потоке пользовательского интерфейса может серьезно повлиять на разработчиков компонентов, таких как я. Цель награды - привлечь больше внимания к этой проблеме и вознаградить @MattSmith, чей ответ помог отследить ее.
я отвечаю за .NET WinFormsUserControl
компонент на основе ActiveX дляустаревшее неуправляемое приложениечерез COM-взаимодействие. Требование времени выполнения: .NET 4.0 + Microsoft.Bcl.Async.
Компонент создается и используется в приложенииS основной поток STA UI. Его реализация используетasync/await
поэтому он ожидает, что экземпляр контекста синхронизации сериализации был установлен в текущем потоке (т.е.WindowsFormsSynchronizationContext
).
Обычно,WindowsFormsSynchronizationContext
настраиваетсяApplication.Run
, где работает цикл сообщений управляемого приложения. Естественно,это не относится к неуправляемому хост-приложениюи я не могу это контролировать. Конечно, хост-приложение все еще имеет свой собственный классический цикл сообщений Windows, поэтому сериализация не должна быть проблемой.await
продолжение колбэков.
Тем не менее, ни одно из решений, которые яМы придумали, что пока все идеально, или даже работает должным образом. Вот'Это искусственный пример, гдеTest
метод вызывается приложением хоста:
Task testTask;
public void Test()
{
this.testTask = TestAsync();
}
async Task TestAsync()
{
Debug.Print("thread before await: {0}", Thread.CurrentThread.ManagedThreadId);
var ctx1 = SynchronizationContext.Current;
Debug.Print("ctx1: {0}", ctx1 != null? ctx1.GetType().Name: null);
if (!(ctx1 is WindowsFormsSynchronizationContext))
SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
var ctx2 = SynchronizationContext.Current;
Debug.Print("ctx2: {0}", ctx2.GetType().Name);
await TaskEx.Delay(1000);
Debug.WriteLine("thread after await: {0}", Thread.CurrentThread.ManagedThreadId);
var ctx3 = SynchronizationContext.Current;
Debug.Print("ctx3: {0}", ctx3 != null? ctx3.GetType().Name: null);
Debug.Print("ctx3 == ctx1: {0}, ctx3 == ctx2: {1}", ctx3 == ctx1, ctx3 == ctx2);
}
Отладочный вывод:
thread before await: 1 ctx1: SynchronizationContext ctx2: WindowsFormsSynchronizationContext thread after await: 1 ctx3: SynchronizationContext ctx3 == ctx1: True, ctx3 == ctx2: False
Хотя это продолжается в той же теме,WindowsFormsSynchronizationContext
контекст ям на текущей ветке доawait
сбрасывается на значение по умолчаниюSynchronizationContext
после этого по какой-то причине.
Почему он сбрасывается? Я'Убедитесь, что мой компонент - единственный компонент .NET, используемый этим приложением. Само приложение звонитCoInitialize/OleInitialize
properly.I»
мы также пыталисьнастройкаWindowsFormsSynchronizationContext
в конструкторе статического одноэлементного объекта, так что он устанавливается в потоке, когда загружается моя управляемая сборка. Это неt help: когдаTest
позже вызывается в том же потоке, контекст уже сброшен до значения по умолчанию.
м с учетом использованиязаказной ожидающий планироватьawait
обратные вызовы черезcontrol.BeginInvoke
моего контроля, так что выше будет выглядетьawait TaskEx.Delay().WithContext(control)
, Это должно работать для меняawaits
До тех пор, пока хост-приложение продолжает качать сообщения, но не дляawaits
в любой из сторонних сборок на мою сборку могут ссылаться.
Я все еще исследую это.Любые идеи о том, как сохранить правильное сродство потока дляawait
в этом случае будет оценено.