2013-05-20 11 views
34

नीचे दिया गया कोड ताज़ा बनाया गया विजुअल स्टूडियो 2012 .NET 4.5 WebAPI प्रोजेक्ट में जोड़ा गया था।Thread.CurrentPrincipal के लिए सही ढंग से प्रवाह करने के लिए "प्रतीक्षा टास्क.इल्ड()" का इंतजार क्यों है?

मैं एसिंक्रोनस विधि में HttpContext.Current.User और Thread.CurrentPrincipal दोनों को असाइन करने का प्रयास कर रहा हूं। Thread.CurrentPrincipal असाइनमेंट गलत तरीके से बहता है जब तक कि await Task.Yield(); (या कुछ और एसिंक्रोनस) निष्पादित नहीं किया जाता है (true से AuthenticateAsync() उत्तीर्ण हो जाएगा)।

वह क्यों है?

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

नोट्स:

  • await Task.Yield();AuthenticateAsync() में कहीं भी प्रदर्शित कर सकते हैं या इसे AuthenticateAsync() करने के लिए कॉल के बाद GetAsync() में ले जाया जा सकता है और यह अभी भी सफल होगा।
  • ApiController.UserThread.CurrentPrincipal देता है।
  • HttpContext.Current.User हमेशा await Task.Yield() के बिना भी सही ढंग से बहता है।
  • Web.config में <httpRuntime targetFramework="4.5"/> शामिल हैं जो impliesUseTaskFriendlySynchronizationContext शामिल हैं।
  • मैंने कुछ दिन पहले a similar question से पूछा था, लेकिन यह नहीं पता था कि उदाहरण केवल सफल रहा क्योंकि Task.Delay(1000) मौजूद था।
+0

यदि आप इसे छोड़ देते हैं तो वास्तव में क्या होता है? – SLaks

+0

@ एसएलएक्स, यदि 'टास्क.इल्ड() 'का इंतजार है, तो थ्रेड। कंटेंट प्रिंसिपल' वापस प्रमाणीकरणएसिंक()' का इंतजार करने से पहले जो कुछ भी था, उसे वापस कर देता है। चूंकि 'थ्रेड। कंटेंट प्रिंसिपल' अब 'माई प्रिंसिपल' नहीं है, अपवाद फेंक दिया गया है। –

+0

मेरे ओविन मिडलवेयर में, मुझे मिडलवेयर के आखिरी टुकड़े में श्रृंखला बनाना पड़ा जो कि टास्क.इल्ड(); ऐसा लगता है कि थ्रेड। कंटेंट प्रिंसिपल को निष्पादन के बाद बाद में उम्मीद की जा सकती है। – BrettJ

उत्तर

37

कितना दिलचस्प है! ऐसा लगता है कि Thread.CurrentPrincipalतार्किक कॉल संदर्भ पर आधारित है, प्रति-थ्रेड कॉल संदर्भ नहीं। आईएमओ यह काफी अनजान है और मुझे यह जानकर उत्सुकता होगी कि इसे इस तरह क्यों लागू किया गया था।


नेट में 4.5।, async तरीकों तार्किक कॉल संदर्भ के साथ बातचीत ताकि यह अधिक उचित रूप से async तरीकों के साथ प्रवाह होगा। मेरे पास blog post on the topic है; AFAIK यह एकमात्र ऐसा स्थान है जहां इसे दस्तावेज किया गया है। .NET 4.5 में, प्रत्येक async विधि की शुरुआत में, यह अपने लॉजिकल कॉल संदर्भ के लिए "कॉपी-ऑन-राइट" व्यवहार सक्रिय करता है। जब (अगर) तार्किक कॉल संदर्भ संशोधित किया जाता है, तो यह पहले स्वयं की एक स्थानीय प्रतिलिपि बना देगा।

आप घड़ी विंडो में System.Threading.Thread.CurrentThread.ExecutionContextBelongsToCurrentScope देखकर तार्किक कॉल संदर्भ (यानी, चाहे इसकी प्रतिलिपि बनाई गई है) की "स्थानीयता" देख सकते हैं।

आप नहीं Yield, फिर जब आप Thread.CurrentPrincipal निर्धारित करते हैं, आप तार्किक कॉल संदर्भ है, जो कि async विधि के रूप में "स्थानीय" व्यवहार किया जाता है की एक प्रति बना रहे करते हैं। जब async विधि लौटाती है, तो उस स्थानीय संदर्भ को त्याग दिया जाता है और मूल संदर्भ इसकी जगह लेता है (आप ExecutionContextBelongsToCurrentScopefalse पर लौट सकते हैं) देख सकते हैं।

दूसरी तरफ, यदि आप Yield करते हैं, तो SynchronizationContext व्यवहार खत्म हो जाता है। वास्तव में क्या होता है कि HttpContext पर कब्जा कर लिया जाता है और दोनों विधियों को फिर से शुरू करने के लिए उपयोग किया जाता है। इस मामले में, आप देख रहे हैं Thread.CurrentPrincipalAuthenticateAsync से GetAsync से संरक्षित; विधियों को फिर से शुरू करने से पहले वास्तव में क्या हो रहा है HttpContext is preserved, and then HttpContext.User is overwriting Thread.CurrentPrincipal है।

आप GetAsync में Yield ले जाते हैं, आप समान व्यवहार देखें: के रूप में एक स्थानीय संशोधन AuthenticateAsync के दायरे वाला Thread.CurrentPrincipal व्यवहार किया जाता है; जब यह विधि वापस आती है तो यह उसके मूल्य को उलट देती है। हालांकि, HttpContext.User अभी भी सही तरीके से सेट है, और वह मान Yield द्वारा कैप्चर किया जाएगा और जब विधि फिर से शुरू हो जाएगी, तो यह Thread.CurrentPrincipal को ओवरराइट कर देगा।

+1

हाय! क्या आपने कभी सुना है कि इसे इस तरह क्यों लागू किया गया था? मैंने इस पोस्ट को पहले से 3 बार पढ़ा है और यह अभी भी मेरा दिमाग उड़ा रहा है। – vtortola

+2

@ vtortola: मुझे निश्चित रूप से पता नहीं है। मुझे लगता है कि ऐसा था कि उपयोगकर्ता अनुमतियां पृष्ठभूमि थ्रेड पर स्वचालित रूप से बहती रहेंगी। यह शायद एक दशक पहले किया गया था, और प्रति-संदर्भ-पर-अद्यतन व्यवहार हाल ही में बहुत अधिक है। तो उन्होंने इस अजीब तरीके से संघर्ष किया। –

संबंधित मुद्दे

 संबंधित मुद्दे