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/await
wird erwartet, dass eine Instanz eines Serialisierungssynchronisierungskontexts auf dem aktuellen Thread installiert wurde (d. h.WindowsFormsSynchronizationContext
).
Meistens,WindowsFormsSynchronizationContext
wird eingerichtet vonApplication.Run
Hier 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.