37 votes

Pourquoi un "wait Task.Yield ()" est-il requis pour que Thread.CurrentPrincipal se déroule correctement?

Le code ci-dessous a été ajouté à une fraîchement créé Visual Studio 2012 .NET 4.5 WebAPI projet.

Je suis en train d'affecter HttpContext.Current.User et Thread.CurrentPrincipal dans une méthode asynchrone. La cession de l' Thread.CurrentPrincipal circule mal, à moins d'un await Task.Yield(); (ou autre chose asynchrone) est exécutée (passage d' true de AuthenticateAsync() entraînera succès).

Pourquoi est-ce?

using System.Security.Principal;
using System.Threading.Tasks;
using System.Web.Http;

namespace ExampleWebApi.Controllers
{
    public class ValuesController : ApiController
    {
        public async Task GetAsync()
        {
            await AuthenticateAsync(false);

            if (!(User is MyPrincipal))
            {
                throw new System.Exception("User is incorrect type.");
            }
        }

        private static async Task AuthenticateAsync(bool yield)
        {
            if (yield)
            {
                // Why is this required?
                await Task.Yield();
            }

            var principal = new MyPrincipal();
            System.Web.HttpContext.Current.User = principal;
            System.Threading.Thread.CurrentPrincipal = principal;
        }

        class MyPrincipal : GenericPrincipal
        {
            public MyPrincipal()
                : base(new GenericIdentity("<name>"), new string[] {})
            {
            }
        }
    }
}

Notes:

  • L' await Task.Yield(); peuvent apparaître n'importe où en AuthenticateAsync() ou il peut être déplacé en GetAsync() après l'appel à AuthenticateAsync() et il faudra encore réussir.
  • ApiController.User retours Thread.CurrentPrincipal.
  • HttpContext.Current.User coule toujours correctement, même sans await Task.Yield().
  • Web.config inclut <httpRuntime targetFramework="4.5"/> ce qui implique UseTaskFriendlySynchronizationContext.
  • J'ai demandé à une question similaire il y a quelques jours, mais ne savais pas que l'exemple était que de succès parce qu' Task.Delay(1000) était présent.

41voto

Stephen Cleary Points 91731

Comme c'est intéressant! Il semble que l' Thread.CurrentPrincipal est basé sur la logique contexte d'appel, pas la par thread contexte d'appel. IMO c'est assez peu intuitive et je serais curieux de savoir pourquoi il a été mis en œuvre cette manière.


Dans .NET 4.5., async méthodes d'interagir avec la logique contexte d'appel, de sorte qu'il sera plus correctement flux avec async méthodes. J'ai un blog sur le thème; autant que je sache, c'est le seul endroit où il est documenté. Dans .NET 4.5, au début de chaque async méthode, il active un "copy-on-write" pour sa logique contexte d'appel. Quand (si) la logique contexte d'appel est modifié, il va créer une copie locale d'abord.

Vous pouvez voir le "caractère local" de la logique contexte d'appel (c'est à dire, s'il a été copié), en observant System.Threading.Thread.CurrentThread.ExecutionContextBelongsToCurrentScope dans une fenêtre d'observation.

Si vous n'avez pas Yield, puis lorsque vous définissez Thread.CurrentPrincipal, vous êtes en train de créer une copie de la logique contexte d'appel, qui est traitée comme un "local" pour qui async méthode. Lorsque l' async méthode retourne, que le contexte local est jeté et le contexte d'origine, prend sa place (vous pouvez voir ExecutionContextBelongsToCurrentScope de retour à l' false).

D'autre part, si vous ne Yield, puis l' SynchronizationContext comportement prend le dessus. Ce qui se passe réellement est que l' HttpContext est captée et utilisée pour reprendre les deux méthodes. Dans ce cas, vous êtes de ne pas voir Thread.CurrentPrincipal préservé de l' AuthenticateAsync de GetAsync; ce qui se passe réellement est - HttpContext est conservé, et puis, HttpContext.User d'écrasement Thread.CurrentPrincipal avant les méthodes de résumé.

Si vous déplacez l' Yield en GetAsync, vous voyez un comportement similaire: Thread.CurrentPrincipal est considérée comme une modification locale étendue AuthenticateAsync; il reprend sa valeur lorsque cette méthode retourne. Toutefois, HttpContext.User est encore correct, et que la valeur sera capturé par Yield et lorsque la méthode reprend, il remplacera Thread.CurrentPrincipal.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X