2015-08-25 5 views
8

Ich habe eine WinForms-App, und ich habe etwas Code, der auf dem UI-Thread ausgeführt werden muss. Der Code nach dem await läuft jedoch auf einem anderen Thread.erwarten, ohne ConfigureAwait (falsch) in einem anderen Thread weiter

protected override async void OnHandleCreated(EventArgs e) 
{ 
    base.OnHandleCreated(e); 

    // This runs on the UI thread. 
    mainContainer.Controls.Clear(); 

    var result = await DoSomethingAsync(); 

    // This also needs to run on the UI thread, but it does not. 
    // Instead it throws an exception: 
    // "Cross-thread operation not valid: Control 'mainContainer' accessed from a thread other than the thread it was created on" 
    mainContainer.Controls.Add(new Control()); 
} 

Ich habe auch versucht explizit ConfigureAwait(true) Zugabe, aber es macht keinen Unterschied. Mein Verständnis war, dass, wenn ich ConfigureAwait(false) weglasse, sollte die Fortsetzung auf dem ursprünglichen Thread laufen. Ist das in manchen Situationen falsch?

Ich habe auch festgestellt, dass, wenn ich ein Steuerelement vor der Wartezeit auf die Sammlung hinzufügen, dann läuft die Fortsetzung magisch auf dem richtigen Thread.

protected override async void OnHandleCreated(EventArgs e) 
{ 
    base.OnHandleCreated(e); 

    // This runs on the UI thread. 
    mainContainer.Controls.Add(new Control()); 
    mainContainer.Controls.Clear(); 

    var result = await DoSomethingAsync(); 

    // This also runs on the UI thread now. Why? 
    mainContainer.Controls.Add(new Control()); 
} 

Meine Frage ist:

  1. Warum ist das passiert?
  2. Wie kann ich die Fortsetzung davon überzeugen, auf dem UI-Thread zu laufen (idealerweise ohne meinen Hack ein Steuerelement hinzuzufügen und es zu entfernen)?

Als Referenz sind hier die wichtigen Teile von DoSomethingAsync. Es sendet eine HTTP-Anfrage mit RestSharp.

protected async Task DoSomethingAsync() 
{ 
    IRestRequest request = CreateRestRequest(); 

    // Here I await the response from RestSharp. 
    // Client is an IRestClient instance. 
    // I have tried removing the ConfigureAwait(false) part, but it makes no difference. 
    var response = await Client.ExecuteTaskAsync(request).ConfigureAwait(false); 

    if (response.ResponseStatus == ResponseStatus.Error) 
     throw new Exception(response.ErrorMessage ?? "The request did not complete successfully."); 

    if (response.StatusCode >= HttpStatusCode.BadRequest) 
     throw new Exception("Server responded with an error: " + response.StatusCode); 

    // I also do some processing of the response here; omitted for brevity. 
    // There are no more awaits. 
} 
+1

Warum denkst du läuft das auf einem anderen Thread? Wo läuft dieser Code? Ereignishandler? – i3arnon

+0

Ich weiß, dass es in einem anderen Thread ausgeführt wird, da der Code nach dem Warten eine Ausnahme auslöst: "Cross-Thread-Operation nicht gültig: Control 'mainContainer' zugegriffen von einem anderen Thread als der Thread, auf dem es erstellt wurde". –

+0

Ja, der Code wird in einem Ereignishandler ausgeführt. Ich werde meine Frage aktualisieren. –

Antwort

1

Es scheint, dass etwas Seltsames mit OnHandleCreated geschieht. Meine Lösung war stattdessen OnLoad zu verwenden. Ich bin ziemlich glücklich mit dieser Lösung, weil es wirklich keinen Grund gibt, OnHandleCreated in meiner Situation zu verwenden.

Ich bin immer noch neugierig, warum dies passiert, also wenn jemand weiß, zögern Sie nicht, eine andere Antwort zu posten.

Edit:

fand ich das eigentliche Problem: es stellt sich heraus, dass ich Form.ShowDialog() nach einer ConfigureAwait(false) rief. Als solches wurde das Formular auf dem UI-Thread erstellt, aber dann rief ich ShowDialog auf einem Nicht-UI-Thread an. Ich bin überrascht, dass das überhaupt funktioniert hat.

Ich habe die ConfigureAwait(false) entfernt, so jetzt ShowDialog wird auf dem UI-Thread aufgerufen.

+0

Könnten Sie 'Debug.WriteLine (new {SynchronizationContext.Current})' direkt vor 'asbest DoSomethingAsync() 'in' OnHandleCreated' platzieren? Wird "System.Windows.Forms.WindowsFormsSynchronizationContext" (erwartet) oder "System.Threading.SynchronizationContext" angezeigt? – Noseratio

+1

Ich bekomme einen 'System.Threading.SynchronizationContext' vor dem Warten und null nach dem Warten. Ich bin mir nicht sicher, warum ich gestern einen Wert ungleich Null bekommen habe. Aber ich denke, das erklärt es. Ich bemerkte auch, dass, wenn ich den gleichen Code im selben Projekt auf einem anderen Zweig in unserem Repo ausführen, ich ein 'System.Windows.Forms.WindowsFormsSynchronizationContext ', und alles funktioniert ordnungsgemäß. Ich frage mich, ob es etwas damit zu tun hat, wie das Formular initialisiert wird. –

+0

Mit Ihrer letzten Änderung sollten Sie Ihre Antwort als akzeptiert markieren, da sie erklärt, was tatsächlich passiert ist.Ansonsten ist es sicher zu 'warten' in' OnHandleCreated', siehe mein [comment] (http://stackoverflow.com/questions/32211598/await-without-configureawaitfalse-continues-on-a-different-thread/32213206?noredirect) = 1 # comment52321127_32213644) zu Stephens Antwort. – Noseratio

7

Mein Verständnis war, dass, wenn ich ConfigureAwait (falsch) weglassen, sollte die Fortsetzung auf dem ursprünglichen Thread ausgeführt werden. Ist das in manchen Situationen falsch?

Was tatsächlich passiert ist, dass await den aktuellen Kontext standardmäßig zu erfassen, und verwenden Sie diesen Kontext die async Verfahren fortzusetzen. Dieser Kontext ist SynchronizationContext.Current, es sei denn, es ist null, in diesem Fall ist es TaskScheduler.Current (in der Regel der Thread-Pool-Kontext). Die meiste Zeit hat der UI-Thread eine UI SynchronizationContext - im Fall von WinForms eine Instanz von WinFormsSynchronizationContext.

Ich habe auch festgestellt, dass, wenn ich ein Steuerelement vor dem warten auf die Sammlung hinzufügen, dann läuft die Fortsetzung magisch auf den richtigen Thread.

Kein Thread beginnt automatisch mit SynchronizationContext. Die WinForms SynchronizationContext wird auf Anforderung installiert, wenn das erste Steuerelement erstellt wird. Aus diesem Grund sehen Sie nach dem Erstellen eines Steuerelements die Fortsetzung in einem Benutzeroberflächenthread.

Da der Umstieg auf eine praktikable Lösung ist, empfehle ich Ihnen, einfach damit zu gehen. Die einzige andere Möglichkeit (vor dem Erstellen eines Steuerelements im Benutzeroberflächenthread fortzusetzen) besteht darin, manuell ein Steuerelement vor dem ersten await zu erstellen.

+0

Tatsächlich wird 'WindowsFormsSynchronizationContext' in' Control's Konstruktor [hier] installiert (http://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/ System/WinForms/Control.cs, 535), so sollte es bereits dort sein, wenn 'OnHandleCreated' aufgerufen wird. Das muss etwas anderes sein. Es gibt einen möglicherweise verwandten [Bug] (https://connect.microsoft.com/VisualStudio/feedback/details/806432/application-doevents-resets-the-threads-current-synchronization-context), aber in diesem Fall beide OnHandleCreated 'und' OnLoad' wären betroffen. – Noseratio

Verwandte Themen