2017-04-24 25 views
7

का इंतजार करने के बाद HttpContext.Current खो देता है। मेरे पास एक एएसपी.NET ऐप लक्ष्यीकरण .NET 4.6 है और मैं यह समझने की कोशिश कर रहा हूं कि क्यों HttpContext.Current मेरे एसिंक एमवीसी नियंत्रक के अंदर पहली प्रतीक्षा के बाद शून्य हो गया कार्रवाई।एएसपी.नेट 4.6 एसिंक नियंत्रक विधि

मैंने जांच की है और तीन बार जांच की है कि मेरी परियोजना v4.6 को लक्षित कर रही है और web.config की targetFramework विशेषता 4.6 है।

SynchronizationContext.Current प्रतीक्षा से पहले और बाद में दोनों को सौंपा गया है और यह सही है, यानी AspNetSynchronizationContext, विरासत में नहीं।

एफडब्ल्यूआईडब्ल्यू, सवाल में प्रतीक्षा थ्रेड को निरंतरता पर स्विच करता है, जो शायद इस तथ्य के कारण है कि यह बाहरी I/O-bound कोड (एक async डेटाबेस कॉल) को आमंत्रित करता है लेकिन यह कोई समस्या नहीं होनी चाहिए, AFAIU।

और फिर भी, यह है! तथ्य यह है कि HttpContext.Current शून्य हो जाता है मेरे कोड के लिए लाइन के नीचे कई समस्याएं होती हैं और इससे मुझे कोई समझ नहीं आती है।

मैंने the usual recommendations चेक किया है और मैं सकारात्मक हूं कि मैं जो कुछ भी कर रहा हूं वह कर रहा हूं। मेरे पास कोड में बिल्कुल ConfigureAwait है!

मैं पास क्या है, मेरे HttpApplication उदाहरण पर async ईवेंट हैंडलर्स की एक जोड़ी है:

public MvcApplication() 
{ 
    var helper = new EventHandlerTaskAsyncHelper(Application_PreRequestHandlerExecuteAsync); 
    AddOnPreRequestHandlerExecuteAsync(helper.BeginEventHandler, helper.EndEventHandler); 

    helper = new EventHandlerTaskAsyncHelper(Application_PostRequestHandlerExecuteAsync); 
    AddOnPostRequestHandlerExecuteAsync(helper.BeginEventHandler, helper.EndEventHandler); 
} 

मैं कस्टम प्राधिकरण & सफाई तर्क है, जो async की आवश्यकता की वजह से इन दोनों की जरूरत है। AFAIU, यह समर्थित है और एक समस्या नहीं होनी चाहिए।

मैं देख रहा हूं कि इस परेशानीपूर्ण व्यवहार का कारण क्या हो सकता है?

अद्यतन: अतिरिक्त अवलोकन।

SynchronizationContext संदर्भ प्रतीक्षा करने से पहले प्रतीक्षा बनाम वही रहता है। लेकिन इसके आंतरिक अंदर के रूप में स्क्रीनशॉट में देखा जा सकता है के बीच में बदल जाते हैं!

का इंतजार करने से पहले: Before entering await

इंतजार के बाद: After continuing

मुझे यकीन है कि कैसे (या यहां तक ​​कि अगर) इस इस बिंदु पर मेरी समस्या के लिए प्रासंगिक हो सकता है नहीं कर रहा हूँ। उम्मीद है कि कोई और इसे देख सकता है!

+1

क्या आप अपने नियंत्रक कार्यों पर 'कॉन्फ़िगरएवाइट (झूठी)' का आह्वान कर रहे हैं? –

+0

@ पाउलो: नहीं, मैं नहीं हूं। – aoven

+1

@Eldho: उस बिंदु का क्या होगा? मुझे नियंत्रक कार्रवाई पर एसिंक चाहिए और वास्तव में आंतरिक क्योंकि (यानी डीबी कॉल मैंने उल्लेख किया है, एक उदाहरण के रूप में) async हैं। एसिंक को हटाने के लिए मुझे कोड की एक बड़ी मात्रा को अक्षम करने की आवश्यकता होगी। – aoven

उत्तर

0

मैंने HttpContext.Current पर एक घड़ी को परिभाषित करने का निर्णय लिया और यह देखने के लिए इंतजार करना शुरू कर दिया कि यह वास्तव में कहां बदलता है। कोई आश्चर्य की बात नहीं है कि धागे को कई बार स्विच किया गया था, जिस पर मैंने आगे बढ़ना शुरू किया था, जिसने मुझे समझ में आया क्योंकि रास्ते में कई सच्चे एसिंक कॉल थे। वे सभी HttpContext.Current उदाहरण को संरक्षित करते हैं जैसा कि उन्हें माना जाता है।

और फिर मैं हमलावर लाइन मारा ...

var observer = new EventObserver(); 
using (EventMonitor.Instance.Observe(observer, ...)) 
{ 
    await plan.ExecuteAsync(...); 
} 

var events = await observer.Task; // Doh! 

संक्षिप्त विवरण है कि plan.ExecuteAsync चरणों जो एक समर्पित धागा के माध्यम से एक गैर अवरुद्ध ढंग से एक विशेष ईवेंट लॉग करने के लिए रिपोर्ट कर रहे हैं की एक संख्या करता है । यह व्यवसाय सॉफ्टवेयर है, रिपोर्टिंग घटनाओं का पैटर्न काफी व्यापक रूप से पूरे कोड में उपयोग किया जाता है।ज्यादातर समय, इन घटनाओं को कॉलर को कोई सीधी चिंता नहीं होती है। लेकिन एक या दो स्थान विशेष हैं कि कॉलर जानना चाहेगा कि एक निश्चित कोड निष्पादित करने के परिणामस्वरूप कौन सी घटनाएं हुई हैं। वह तब होता है जब ऊपर देखा गया EventObserver उदाहरण का उपयोग किया जाता है।

await observer.Task सभी प्रासंगिक घटनाओं को संसाधित और निरीक्षण करने के लिए प्रतीक्षा करने के लिए आवश्यक है। प्रश्न में कार्य पर्यवेक्षक के स्वामित्व वाले TaskCompletionSource उदाहरण से आता है। एक बार सभी घटनाओं में उलझने के बाद, स्रोत के SetResult को थ्रेड से बुलाया जाता है जो घटनाओं को संसाधित करता है। इस विस्तार के अपने मूल कार्यान्वयन था - बहुत भोलेपन से - इस प्रकार है:

public class EventObserver : IObserver<T> 
{ 
    private readonly ObservedEvents _events = new ObservedEvents(); 

    private readonly TaskCompletionSource<T> _source; 

    private readonly SynchronizationContext _capturedContext; 

    public EventObserver() 
    { 
     _source = new TaskCompletionSource<T>(); 

     // Capture the current synchronization context. 
     _capturedContext = SynchronizationContext.Current; 
    } 

    void OnCompleted() 
    { 
     // Apply the captured synchronization context. 
     SynchronizationContext.SetSynchronizationContext(_capturedContext); 
     _source.SetResult(...); 
    } 
} 

अब मैं देख सकता हूँ कि बुला SetSynchronizationContextSetResult से पहले नहीं कर रहा है कि मैं क्या आशा व्यक्त की यह होगा। लक्ष्य await observer.Task लाइन की निरंतरता के लिए मूल सिंक्रनाइज़ेशन संदर्भ लागू करना था।

सवाल अब है: मैं इसे ठीक से कैसे कर सकता हूं? मुझे लगता है कि यह एक स्पष्ट ContinueWith कहीं कॉल करेगा।

अद्यतन

यहाँ मैं क्या किया है। मैं TaskCreationOptions.RunContinuationsAsynchronously विकल्प TaskCompletionSource ctor पारित कर दिया और मेरे EventObserver वर्ग पर टास्क संपत्ति संशोधित स्पष्ट रूप से सिंक्रनाइज़ निरंतरता शामिल करने के लिए: तो अब, जब एक कोड कॉल await observer.Task, निरंतरता सुनिश्चित करें कि सही संदर्भ पहले दर्ज किया गया है कर देगा

public Task<T> Task 
{ 
    get 
    { 
     return _source.Task.ContinueWith(t => 
     { 
      if (_capturedContext != null) 
      { 
       SynchronizationContext.SetSynchronizationContext(_capturedContext); 
      } 

      return t.Result; 
     }); 
    } 
} 

। अब तक, ऐसा लगता है कि यह सही ढंग से काम कर रहा है!

+2

एक और विकल्प सिंक्रनाइज़ेशन संदर्भ में 'SetResult' को कॉल पोस्ट करना होगा। कुछ ऐसा: '_capturedContext.Post (_ => _source.SetResult (...), शून्य);' –

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