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 в этом случае будет оценено.

Ответы на вопрос(2)

Ваш ответ на вопрос