Warten und SynchronisierenKontext in einer verwalteten Komponente, die von einer nicht verwalteten App gehostet wird

[EDITED] Dies scheint ein Fehler zu sein in der Umsetzung des Frameworks vonAnwendung.Veranstaltungen, die ich gemeldet habeHier. Das Wiederherstellen eines falschen Synchronisierungskontexts in einem UI-Thread kann sich ernsthaft auf Komponentenentwickler wie mich auswirken. Das Ziel der Prämie ist es, mehr Aufmerksamkeit auf dieses Problem zu lenken und @MattSmith zu belohnen, dessen Antwort dazu beigetragen hat, es aufzuspüren.

Ich bin verantwortlich für ein .NET WinFormsUserControl-basierte Komponente als ActiveX ausgesetztEine alte, nicht verwaltete App, über COM-Interop. Die Laufzeitanforderung ist .NET 4.0 + Microsoft.Bcl.Async.

Die Komponente wird instanziiert und im Haupt-STA-UI-Thread der App verwendet. Seine Implementierung nutztasync/awaitwird erwartet, dass eine Instanz eines Serialisierungssynchronisierungskontexts auf dem aktuellen Thread installiert wurde (d. h.WindowsFormsSynchronizationContext).

Meistens,WindowsFormsSynchronizationContext wird eingerichtet vonApplication.RunHier wird die Nachrichtenschleife einer verwalteten App ausgeführt. Natürlich,Dies ist bei der nicht verwalteten Host-App nicht der Fallund ich habe keine Kontrolle darüber. Natürlich verfügt die Host-App immer noch über eine eigene klassische Windows-Nachrichtenschleife, sodass die Serialisierung problemlos möglich istawait Fortsetzungsrückrufe.

Keine der Lösungen, die ich bisher entwickelt habe, ist jedoch perfekt oder funktioniert sogar einwandfrei. Hier ist ein künstliches Beispiel, woTest Methode wird von der Host-App aufgerufen:

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);
}

Debug-Ausgabe:

thread before await: 1
ctx1: SynchronizationContext
ctx2: WindowsFormsSynchronizationContext
thread after await: 1
ctx3: SynchronizationContext
ctx3 == ctx1: True, ctx3 == ctx2: False

Obwohl es auf demselben Thread weitergeht, ist dasWindowsFormsSynchronizationContext Kontext Ich installiere auf dem aktuellen Thread vorawait wird auf den Standard zurückgesetztSynchronizationContext danach aus irgendeinem Grund.

Warum wird es zurückgesetzt? Ich habe überprüft, ob meine Komponente die einzige .NET-Komponente ist, die von dieser App verwendet wird. Die App selbst ruft anCoInitialize/OleInitialize richtig.

Ich habe es auch versuchteinrichtenWindowsFormsSynchronizationContext im Konstruktor eines statischen Singleton-Objektswird also auf dem Thread installiert, wenn meine verwaltete Assembly geladen wird. Das hat nicht geholfen: wannTest wird später im selben Thread aufgerufen, der Kontext wurde bereits auf den Standard zurückgesetzt.

Ich überlege mir einebenutzerdefinierte Kellner planenawait Rückrufe übercontrol.BeginInvoke von meiner Kontrolle, so würde das oben aussehenawait TaskEx.Delay().WithContext(control). Das sollte für mich funktionierenawaits, solange die Host-App Nachrichten pumpt, aber nicht fürawaits In allen Assemblys von Drittanbietern verweist meine Assembly möglicherweise auf.

Das recherchiere ich noch.Irgendwelche Ideen, wie man die richtige Fadenaffinität für behältawait in diesem szenario wäre ich dankbar.

Antworten auf die Frage(2)

Ihre Antwort auf die Frage