Festlegen von Thread.CurrentPrincipal mit async / await

8

Unten ist eine vereinfachte Version von wo ich Thread.CurrentPrincipal innerhalb einer asynchronen Methode auf ein benutzerdefiniertes UserPrincipal-Objekt festlegen möchte, aber das benutzerdefinierte Objekt verliert nach dem Verlassen der Wartezeit verloren, obwohl es immer noch auf der neuen ThreadID 10 ist.

Gibt es eine Möglichkeit, Thread.CurrentPrincipal in einem Wartefeld zu ändern und später zu verwenden, ohne es weiterzugeben oder zurückzugeben? Oder ist das nicht sicher und sollte niemals asynchron sein? Ich weiß, es gibt Thread-Änderungen, aber dachte async / erwarten würde synching dies für mich behandeln.

%Vor%     
Sean Merron 10.07.2015, 17:10
quelle

4 Antworten

5

Sie könnten einen benutzerdefinierten Warner verwenden, um CurrentPrincipal (oder beliebige Thread-Eigenschaften) zu übergeben. Das folgende Beispiel zeigt, wie es gemacht werden könnte, inspiriert von Stephen Toubs CultureAwaiter . Es verwendet TaskAwaiter intern, sodass auch der Synchronisationskontext (falls vorhanden) erfasst wird.

Verwendung:

%Vor%

Code (nur sehr leicht getestet):

%Vor%     
Noseratio 12.07.2015, 00:34
quelle
7
  

Ich weiß, dass es Thread-Änderungen gibt, aber dachte async / await würde das für mich synchronisieren.

async / await führt keine Synchronisierung von Thread-lokalen Daten selbst durch. Es hat jedoch einen "Haken", wenn Sie Ihre eigene Synchronisierung durchführen möchten.

Wenn Sie await einer Aufgabe zuweisen, wird standardmäßig der aktuelle "Kontext" erfasst (was SynchronizationContext.Current ist, es sei denn, es ist null , in diesem Fall ist es TaskScheduler.Current ). Wenn die Methode async fortgesetzt wird, wird sie in diesem Kontext fortgesetzt.

Wenn Sie also einen "Kontext" definieren möchten, können Sie dies tun, indem Sie Ihre eigene SynchronizationContext definieren. Dies ist jedoch nicht gerade einfach. Vor allem, wenn Ihre App auf ASP.NET ausgeführt werden muss, was ein eigenes AspNetSynchronizationContext erfordert (und sie können nicht verschachtelt werden oder irgendetwas - Sie erhalten nur eines). ASP.NET verwendet SynchronizationContext , um Thread.CurrentPrincipal festzulegen.

Beachten Sie jedoch, dass es eine bestimmte Bewegung weg von SynchronizationContext gibt. ASP.NET vNext hat keinen. OWIN hat es nie getan (AFAIK). Self-Hosted SignalR tut auch nicht. Es wird allgemein als geeigneter erachtet, den Wert einige -Wege zu übergeben - ob dies für die Methode explizit ist oder in eine Membervariable des Typs, der diese Methode enthält, eingefügt wird.

Wenn Sie wirklich den Wert nicht übergeben möchten, können Sie auch einen anderen Ansatz wählen: async -Äquivalent von ThreadLocal . Die Kernidee besteht darin, unveränderliche Werte in einem LogicalCallContext zu speichern, das durch asynchrone Methoden entsprechend vererbt wird. Ich behandle dieses "AsyncLocal" auf meinem Blog (es gibt Gerüchte über AsyncLocal kommt möglicherweise in .NET 4.6, aber bis dahin müssen Sie Ihre eigenen rollen). Beachten Sie, dass Sie Thread.CurrentPrincipal nicht mit der AsyncLocal -Technik lesen können. Sie müssten Ihren gesamten Code ändern, um etwas wie MyAsyncValues.CurrentPrincipal zu verwenden.

    
Stephen Cleary 10.07.2015 18:05
quelle
5

Der Thread.CurrentPrincipal wird im ExecutionContext gespeichert, der im Thread Local Storage gespeichert wird.

Beim Ausführen eines Delegaten in einem anderen Thread (mit Task.Run oder ThreadPool.QueueWorkItem) wird der ExecutionContext aus dem aktuellen Thread erfasst und der Delegat wird in ExecutionContext.Run . Wenn Sie also CurrentPrincipal vor dem Aufruf von Task.Run setzen, wird es immer noch im Delegate festgelegt.

Nun ist Ihr Problem, dass Sie das CurrentPrincipal in Task.Run ändern und der ExecutionContext nur in eine Richtung fließt. Ich denke, das ist das erwartete Verhalten in den meisten Fällen, eine Lösung wäre, das CurrentPrincipal am Anfang zu setzen.

Was ursprünglich gewünscht ist, ist nicht möglich, wenn Sie den ExecutionContext in einer Task ändern, da Task.ContinueWith auch den ExecutionContext erfasst. Um das zu tun, müßten Sie den ExecutionContext direkt nach dem Ausführen des Delegates einfangen und dann in einer Fortsetzung des Custom Watcher weiterleiten, aber das wäre sehr böse.

    
Jeff Cyr 10.07.2015 18:49
quelle
2

ExecutionContext , das SecurityContext enthält, enthält CurrentPrincipal , ist ziemlich viel über alle asynchronen Gabeln geflossen. In deinem Task.Run() -Delegaten bekommst du - in einem separaten Thread, den du notierst - den gleichen CurrentPrincipal . Unter der Haube erhalten Sie jedoch den Kontext über ExecutionContext.Run (...) , das besagt:

  

Der Ausführungskontext kehrt in seinen vorherigen Zustand zurück, wenn der   Methode wird abgeschlossen.

Ich finde mich in einem fremden Gebiet, das sich mit Stephen Cleary unterscheidet :), aber ich sehe nicht, wie SynchronizationContext damit zu tun hat.

Stephen Toub deckt das meiste in einem exzellenten Artikel hier ab .

    
sellotape 10.07.2015 21:18
quelle