2017-12-21 32 views
2

Ich habe ein Xamarin.Android-Projekt, TargetFramework = Android-Version: 8.0 (Oreo).Xamarin Android - Task.Run vs Task.Factory.StartNew und Thread.CurrentPrincipal

Ich habe folgendes Problem bemerkt:

  1. Set Thread.CurrentPrincipal zu einem benutzerdefinierten Prinzipal (besonders vorsichtig sein, um sicherzustellen, dass es serializable ist!)
  2. Anruf await Task.Run() einige Aufgaben auszuführen.
  3. Innerhalb der Task, wenn es auf einem anderen Thread ausgeführt wird, ist der Thread.CurrentPrincipal nicht festgelegt.
  4. Wenn Sie Task.Factory.StartNew() anstelle von Task.Run() verwenden, wird Thread.CurrentPrincipal für den Task, der im Hintergrundthread ausgeführt wird, korrekt festgelegt.

Mit anderen Worten, Task.Factory.StartNew die CurrentPrinciple auf neue Themen zu fließen scheint, wo, wie Task.Run nicht

Außerdem, wenn Sie diesen Test wiederholen Sie auf NET 4.7, Thread.CurrentPrincipal fließt in beiden Szenarien richtig, so dass dieser Unterschied im Verhalten nur bei Mono/Xamarin.Android erscheint.

Hier ist ein Testfall:

[Fact] 
    public async Task LoginService_WhenUserLogsIn_PrincipalFlowsToAsyncTask() 
    {  

     var mockIdentity = new MockIdentity(true); 
     var mockPrincipal = new MockPrincipal(mockIdentity);   
     Thread.CurrentPrincipal = mockPrincipal ;   

     await Task.Factory.StartNew(async() => 
     { 
      var newThreadId = Thread.CurrentThread.ManagedThreadId; // on different thread. 
      Assert.True(Thread.CurrentPrincipal.Identity.IsAuthenticated); 
      Assert.Equal(mockPrincipal, Thread.CurrentPrincipal); 

      await Task.Factory.StartNew(() => 
      { 
       // still works even when nesting.. 
       newThreadId = Thread.CurrentThread.ManagedThreadId; 
       Assert.True(Thread.CurrentPrincipal.Identity.IsAuthenticated); 
       Assert.Equal(mockPrincipal, Thread.CurrentPrincipal); 

      }, TaskCreationOptions.LongRunning); 

     }, TaskCreationOptions.LongRunning); 

     await Task.Run(() => 
     { 
      // Following works on NET4.7 and fails under Xamarin.Android. 
      var newThreadId = Thread.CurrentThread.ManagedThreadId; 
      Assert.True(Thread.CurrentPrincipal.Identity.IsAuthenticated); 
      Assert.Equal(mockPrincipal, Thread.CurrentPrincipal); 

     }); 

    } 

Hier sind einige Screenshots, während das Debuggen unter Xamarin.Android Anwendung, die folgenden Dinge an verschiedenen Punkten zeigt:

  1. Die Thread.CurrentPrinicpal
  2. Der Thread.CurrentThread.ManagedThreadId
  3. TaskScheduler.Default
  4. TaskScheduler.Current
  5. TaskScheduler.FromCurrentSynchronizationContext();

Dies ist, was sieht es wie vor StartNew():

enter image description here

Hier ist, was im Inneren sieht es wie StartNew():

enter image description here

Beachten Sie, dass die Aufgabe Geplant über StartNew() wird in einem Thread-Pool-Thread ausgeführt, und der Task-Scheduler ShcnronisationContext ist null, da es kein Cu gibt rrent Synchronization Context an dieser Stelle. Beachten Sie jedoch, dass der Prinzipal des Threads korrekt auf "Daz" Identität festgelegt ist. Hier

ist, was die Dinge aussehen, nach Task.Factory.StartNew wartet() und vor dem Aufruf Task.Run():

enter image description here

Bitte beachte, dass wir wieder zurück auf den Hauptthread sind, nur wie wir vor dem Aufruf von StartNew() waren.

Es scheint jedoch der SyncContext TaskScheduler hat sich geändert (ID ist jetzt 3).Nicht sicher, ob das für dieses Problem relevant ist.

Jetzt ist es das, was die Dinge aussehen während Task.Run():

enter image description here

Hinweis: Die Thread.CurrentPrincipal.IdentityName verloren wurde (dh, weil die Prinicpal nicht auf diesen Thread geflossen als mit StartNew().

Wir sind immer noch auf einem Hintergrundthread, wie wir mit StartNew() waren. Es gibt keinen SynchronizationContext auf diesem Thread, was wir mit einem Hintergrundthread erwarten, und war der Gleiches innerhalb von StartNew() also kein Unterschied dort. Die Taskplaner von Default und Current sehen gleich aus (beide ID = 1), also auch dort kein Unterschied.

Der einzige Unterschied, den ich sehen kann, ist, dass der Auftraggeber nicht zum Thread geflossen ist.

Was ist der Grund? Ist das ein Fehler? Ich dachte Thread.CurrentPrinicpal sollte immer mit dem ExecutionContext fließen, wenn Sie Task.Run() verwenden.

Ich habe warf auch ein Problem auf Github hier: https://github.com/xamarin/xamarin-android/issues/1130

UPDATE: https://github.com/mono/mono/pull/6326/files

+0

In Ihrem 'StartNew' befinden Sie sich immer noch auf demselben' SyncContext' wie zuvor, d. H. Dem Anfragethread. Sofern nicht anders angegeben, wird 'StartNew' auf dem aktuellen' SycnContext' ausgeführt. 'Task.Run' läuft jedoch auf einem Standardkontext, d. H. Hintergrund-Thread, und Sie sehen, dass Ihr Prinzip nicht mit gebracht wird. – JSteward

+0

@JSteward - in meinem 'StartNew' läuft es auf einem Threadpool-Thread, und es gibt keinen SynchronizationContext. Das CurrentPrincipal des Threads ist korrekt festgelegt, so dass es aus dem aufrufenden Thread geflossen ist. Innerhalb von Task.Run() kann ich jedoch sehen, dass es auch auf einem Hintergrundthread ausgeführt wird, jedoch wurde das Thread.CurrentPrincipal nicht festgelegt. Das ist mein Problem, da unter .NET4.7 genau das Gleiche passiert, außer dass das Thread.CurrentPrincipal in beiden Szenarien richtig eingestellt ist. Also warum ist es anders unter Mono (Xamarin, Android) ist meine eigentliche Frage. – Darrell

+0

@JSteward Ich habe Screenshots hinzugefügt, die detaillierter zeigen, wie der Thread/Scheduler/Sync-Kontext an den verschiedenen Punkten aussieht. – Darrell

Antwort

0

bearbeiten Begin:

Es folgt aussieht wie hat sich als Fehler anerkannt worden ist, wird es hoffentlich mit fixiert werden

Ich werde den Rest meiner Antwort verlassen, obwohl jemand dies lesen sollte beachten Sie, dass es nicht relevant für die Frage ist und ich habe nichts dagegen, es als unbeantwortet zu hängen, aber bitte nicht abmelden. Es gibt darunter Kommunikation, die relevant sein kann.

Ich habe mir die Frage genauer angeschaut und erkannt, was gefragt wird und kann ehrlich zustimmen, dass ich denke, dass dies ein Fehler sein könnte. Ich kann es nicht erklären und fühle, dass es gut ist, vorwärts zu kommen. Ich werde diese Frage selbst im Auge behalten.

bearbeiten Ende:

Ich bin nicht ganz sicher, ob ich verstehe, was du tust. Vielleicht Breakpoints verwenden und über den Variablen schweben? Platziere diese Variablen außerhalb der beiden Aufgaben und sieh sie dann an, nachdem sie ausgeführt wurden.

Aber hier ist die Antwort, die ich denke, dass Sie suchen. Bei der Eingabe von Foo ist es nur eine asynchrone Methode mit einem Rückgabetyp. Ja, das bedeutet, wir können es abwarten, wenn es angerufen wird, aber nichts erwartet, bis es tatsächlich auf einem neuen Thread läuft. Daher bleiben Sie in jedem Kontext, der await Foo() aufruft, bis Foo die await Task.Factory.StartNew erreicht.

Mit dem Aufruf Task.Factory.StartNew wird ein Thread aus dem Threadpool abgerufen und die übergebene Aktion für diesen Thread ausgeführt. Sobald es fertig ist, ist es geplant, zu Foo in demselben Kontext zurückzukehren, in dem es aufgerufen wurde. Nun wartet dieser Kontext erneut auf await Task.Run, was genau dasselbe tut wie await Task.Factory.StartNew. Sie werden einen Thread, aus dem Thread-Pool, und führen Sie die Arbeit dort usw.

Es gibt nur einen Unterschied in den beiden Anrufe und das ist der TaskCreationOptions.LongRunning Parameter an die TaskFactory übergeben ... ehrlich gesagt, was die Fabrik Es ist nicht konkret oder in allen Umgebungen gleich, aber es ist da, um die Fabrik wissen zu lassen, dass Ihr Thread für eine längere Zeit am Leben sein wird.Ich glaube an Windows zieht es den Thread aus einem anderen Pool von Threads, die für eine längere Zeit ausgeführt werden sollen, aber ich bin mir nicht ganz sicher. In jedem Fall; Sie befinden sich immer noch in einem neuen Thread für jeden Anruf, nicht im selben Thread, und Sie befinden sich wieder im selben Thread dazwischen.

Sie können auch .ConfigureAwait(false); am Ende Ihrer await Anrufe verwenden, aber dies wird nicht zum aufrufenden Kontext zurückkehren. Der Thread wird so geplant, dass er weiterhin auf dem aktuellen Stand läuft, was zu Problemen führen kann, wenn Sie sich dessen nicht bewusst sind. Es ist insgesamt effizienter, aber stellen Sie sicher, dass der Rest Ihrer Methode nicht im selben Kontext arbeiten muss.

Hoffe, das hilft.

+1

Danke, ich werde meine Frage aktualisieren und versuchen, sie klarer zu beschreiben. Alles, was ich tue, ist, den aktuellen Thread-Principal zu setzen und dann zu beachten, dass der Principle beim Aufruf von Task.Run() nicht zum Thread führt, auf dem der Task ausgeführt wird (es sei denn, er wurde für den aktuellen Thread geplant)) - aber dann Aufruf von Task.Factory.StartNew() - das PRINIPAL fließt in den neuen Thread. Dieses Verhalten tritt nur bei Xamarin Android (Mono) auf. Auf dem vollständigen .NET-Framework 4.7 fließt der Prinzipal in beiden Fällen korrekt. Ich würde gerne wissen, ob das ein Fehler ist. – Darrell

+0

Ich habe eine hoffentlich detailliertere Erklärung hinzugefügt, mit Screenshots der verschiedenen Punkte beim Debuggen. – Darrell

+0

@Darrell Hey Darrell, ich habe meine Antwort aktualisiert, die keine Antwort ist, und ich schätze es, dass Sie die Frage aktualisiert haben. Ich sehe genau was du jetzt sagst und hast das selbst nicht bemerkt. Ich habe mich auch entschieden ein bisschen zu graben und ich denke du hast recht das ist ein Fehler. Sehr merkwürdig ... Ich habe die Antwort markiert und als Favorit platziert, weil ich sie selbst beantworten möchte. –

Verwandte Themen