Dlaczego jest to zadanie oczekujące.Yield () " wymagane dla wątku.CurrentPrincipal to flow correctly?

Poniższy kod został dodany do świeżo stworzonego projektu Visual Studio 2012. NET 4.5 WebAPI.

Próbuję przypisać zarówno HttpContext.Current.User jak i Thread.CurrentPrincipal metodą asynchroniczną. Przypisanie Thread.CurrentPrincipal przebiega nieprawidłowo, chyba że zostanie wykonane await Task.Yield(); (lub cokolwiek innego asynchronicznego) (Przejście true do AuthenticateAsync() zakończy się sukcesem).

Dlaczego?
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[] {})
            {
            }
        }
    }
}

Uwagi:

  • await Task.Yield(); może pojawić się w dowolnym miejscu w AuthenticateAsync() lub może zostać przeniesiony do GetAsync() po wywołaniu do AuthenticateAsync() i nadal się uda.
  • ApiController.User zwraca Thread.CurrentPrincipal.
  • HttpContext.Current.User zawsze płynie poprawnie, nawet bez await Task.Yield().
  • Web.config zawiera <httpRuntime targetFramework="4.5"/> które implikuje UseTaskFriendlySynchronizationContext.
  • zadałem podobne pytanie kilka dni temu, ale nie zdawałem sobie sprawy, że ten przykład odnosi sukces tylko dlatego, że Task.Delay(1000) jest obecny.
Author: Community, 2013-05-20

1 answers

Ciekawe! Wygląda na to, że Thread.CurrentPrincipal opiera się na kontekście wywołania logicznego, a nie na kontekście wywołania dla poszczególnych wątków. IMO jest to dość nieintuicyjne i byłbym ciekaw, dlaczego zostało to zaimplementowane w ten sposób.


W. NET 4.5., async Metody oddziałują z kontekstem wywołania logicznego tak, że będzie on lepiej płynął z metodami async. Mam wpis na blogu na temat ; AFAIK to jedyne miejsce gdzie jest to udokumentowane. W. NET 4.5, na początku każdego async metoda aktywuje zachowanie "Kopiuj przy zapisie" dla logicznego kontekstu wywołania. Gdy (if) logiczny kontekst wywołania zostanie zmodyfikowany, najpierw utworzy lokalną kopię samego siebie.

Możesz zobaczyć "localness" kontekstu wywołania logicznego (tzn. czy został skopiowany) obserwując System.Threading.Thread.CurrentThread.ExecutionContextBelongsToCurrentScope w oknie obserwacyjnym.

Jeśli nie Yield, to po ustawieniu Thread.CurrentPrincipal tworzysz kopię kontekstu wywołania logicznego, który jest traktowany jako "lokalny" dla tej metody async. Gdy async metoda zwraca, że lokalny kontekst jest odrzucany, a oryginalny kontekst zajmuje jego miejsce (możesz zobaczyć ExecutionContextBelongsToCurrentScope zwracając do false).

Z drugiej strony, jeśli zrobisz Yield, wtedy SynchronizationContext zachowanie przejmuje kontrolę. To, co się dzieje, to to, że HttpContext jest przechwytywane i używane do wznowienia obu metod. W tym przypadku, jesteś Nie widząc Thread.CurrentPrincipal zachowane od AuthenticateAsync do GetAsync; to, co się dzieje, jest HttpContext jest zachowana, a następnie HttpContext.User nadpisuje Thread.CurrentPrincipal przed metody wznowić.

Jeśli przeniesiesz Yield do GetAsync, zobaczysz podobne zachowanie: {[0] } jest traktowana jako lokalny zakres modyfikacji do AuthenticateAsync; odwraca swoją wartość, gdy ta metoda powróci. Jednak {[18] } jest nadal ustawiona poprawnie, a wartość ta zostanie przechwycona przez Yield i gdy metoda zostanie wznowiona, zastąpi ona Thread.CurrentPrincipal.

 37
Author: Stephen Cleary,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-20 17:15:03