2015-06-05 15 views
18

मैं छद्म कोडasync में CurrentCulture रखें/इंतजार

string GetData() 
{ 
    var steps = new List<Task<string>> 
    { 
    DoSomeStep(), 
    DoSomeStep2() 
    }; 

    await Task.WhenAll(steps); 

    return SomeResourceManagerProxy.RetrieveValuesForLocalizedStrings(steps.Select(s => s.Result)); 

} 

इस विधि WebService, जहां मैं उपयोगकर्ता के ब्राउज़र सेटिंग्स के अनुसार Thread.CurrentUICulture सेट से कहा जाता है निम्नलिखित है।

प्रतीक्षा के बाद, CurrentUICulture खो गया है (मैं अलग-अलग धागे पर चलता हूं)।

मैं निम्नलिखित के साथ इस मुद्दे को हल किया है:

public class MyAwaiter<T> : INotifyCompletion 
    { 
     private TaskAwaiter<T> waiter; 
     private CultureInfo culture; 

     public MyAwaiter(TaskAwaiter<T> waiter) 
     { 
      this.waiter = waiter; 
     } 

     public PreserveCultureAwaiter<T> GetAwaiter() { return this; } 

     public bool IsCompleted { get { return waiter.IsCompleted; } } 

     public void OnCompleted(Action continuation) 
     { 
      culture = Thread.CurrentThread.CurrentUICulture; 
      waiter.OnCompleted(continuation); 
     } 

     public T GetResult() 
     { 
      Thread.CurrentThread.CurrentUICulture = culture; 
      return waiter.GetResult(); 
     } 
    } 

    public static MyAwaiter<T> KeepCulture<T>(this Task<T> task) 
    { 
     return new MyAwaiter<T>(task.GetAwaiter()); 
    } 

... 

    await Task.WhenAll(steps).KeepCulture(); 

यह एक दोष यह है - पर KeepCulture() कॉल करने के लिए हर कार्य किया जाता है कि समय से प्रतीक्षा की जा रही याद रखने के लिए की जरूरत है। (यूआई संस्कृति को कार्य में रखने के लिए मेरे पास कुछ विस्तार विधि भी है)।

क्या यूआई संस्कृति को संरक्षित करने का कोई आसान तरीका है?

+0

एक नोट: मैं सामान के स्थानीयकरण के लिए ढांचा लिख ​​रहा हूं जो पहले से ही एसिंक/प्रतीक्षा सामग्री में लिखा गया था। मैं चाहता हूं कि सबकुछ अन्य डेवलपर्स के जितना संभव हो उतना पारदर्शी हो, – nothrow

+0

मैंने कुछ समय पहले .NET रिपॉजिटरीज़ में से एक में एक समान समाधान देखा है। यह ईएफ हो सकता है। मुझे लगता है कि उन्होंने इसे किसी कारण से हटा दिया है, इसलिए आपको लॉग स्कैन करने की आवश्यकता है। – usr

+0

शायद इस क्यू + ए को बंद करने का समय, समस्या ठीक हो गई थी। –

उत्तर

19

संस्कृति .NET Framework, एक बहुत कुख्यात समस्या में बहती नहीं है। विंडोज़ पर हल करना बहुत मुश्किल है, संस्कृति unmanaged property धागे का है, इसलिए सीएलआर यह सुनिश्चित नहीं कर सकता कि यह हमेशा सही ढंग से सेट हो। यह मुख्य धागे पर एक बड़ी वसा गलती पर CurrentCulture के साथ tinkering बनाता है। आपको मिलने वाली बग का निदान करना बहुत मुश्किल है। एक सॉर्टेडलिस्ट की तरह आप एक थ्रेड पर बनाते हैं जिसे अचानक किसी अन्य पर क्रमबद्ध नहीं किया जाता है। छी।

माइक्रोसॉफ्ट ने .NET 4.5 में इसके बारे में कुछ किया, उन्होंने CultureInfo.DefaultThreadCurrentCulture संपत्ति को जोड़ा। इसके अलावा DefaultThreadCurrentUICulture। यह अभी भी गारंटी नहीं देता है कि यह सही ढंग से सेट किया जाएगा, अप्रबंधित कोड जिसे आप कॉल कर सकते हैं उसे बदल सकते हैं और सीएलआर इसके बारे में कुछ भी नहीं कर सकता है। दूसरे शब्दों में, निदान करने के लिए एक बग बहुत कठिन होगा। लेकिन कम से कम आपके पास कुछ विचार हो सकता है जब यह बदल सकता है।


अद्यतन: इस समस्या को नेट 4.6 में अच्छी तरह से तय किया गया था, संस्कृति अब एक थ्रेड से दूसरे में बहती है और CultureInfo.DefaultThreadCurrentCulture हैक लंबे समय तक आवश्यक है और न ही उपयोगी नहीं है। CultureInfo.CurrentCulture के लिए एमएसडीएन आलेख में दस्तावेज। जैसा कि अभी लिखा गया है, पूरी तरह से सही नहीं दिखता है, जब मैंने इसका परीक्षण किया तो यह हमेशा बहता था और डिफॉल्ट थ्रेड कंटेंटकल्चर अब और कोई भूमिका निभाता प्रतीत होता है।

+0

मैं आपकी सलाह समझता हूं, लेकिन दुर्भाग्यवश ऐसा नहीं करना कुछ ऐसा नहीं है जो मैं इस पल में नहीं कर सकता – nothrow

+0

मुझे लगता है कि इसे उत्तर के रूप में चिह्नित किया जाना चाहिए। मेरे लिए यह फ्रेमवर्क 4.5 में एक उपयुक्त समाधान प्रतीत होता है। तर्क यह है कि एक अप्रबंधित कॉल संस्कृति को बदल सकती है प्रासंगिक नहीं है। एक प्रबंधित कॉल में डिफ़ॉल्ट भी ओवरराइड किया जा सकता है। या मैं इसे गहराई से समझ नहीं पा रहा हूं। (?) सवाल यह हो सकता है कि किसी भी अप्रबंधित कोड को संस्कृति क्यों बदलनी चाहिए। मुझे लगता है कि आप आमतौर पर अपनी प्रक्रिया में सभी धागे के लिए संस्कृति को परिभाषित करते हैं। –

3

अब तक मैं अपने खुद के SynchronizationContext, जो मैं दोनों ASP.NET और सांत्वना अनुप्रयोगों के साथ परीक्षण किया है बना लिया है, और दोनों में यह संस्कृति रहता है मैं इसे चाहते हैं:

/// <summary> 
/// Class that captures current thread's culture, and is able to reapply it to different one 
/// </summary> 
internal sealed class ThreadCultureHolder 
{ 
    private readonly CultureInfo threadCulture; 
    private readonly CultureInfo threadUiCulture; 

    /// <summary> 
    /// Captures culture from currently running thread 
    /// </summary> 
    public ThreadCultureHolder() 
    { 
     threadCulture = Thread.CurrentThread.CurrentCulture; 
     threadUiCulture = Thread.CurrentThread.CurrentUICulture; 
    } 

    /// <summary> 
    /// Applies stored thread culture to current thread 
    /// </summary> 
    public void ApplyCulture() 
    { 
     Thread.CurrentThread.CurrentCulture = threadCulture; 
     Thread.CurrentThread.CurrentUICulture = threadUiCulture; 
    } 

    public override string ToString() 
    { 
     return string.Format("{0}, UI: {1}", threadCulture.Name, threadUiCulture.Name); 
    } 
} 

/// <summary> 
/// SynchronizationContext that passes around current thread's culture 
/// </summary> 
internal class CultureAwareSynchronizationContext : SynchronizationContext 
{ 
    private readonly ThreadCultureHolder cultureHolder; 
    private readonly SynchronizationContext synchronizationImplementation; 

    /// <summary> 
    /// Creates default SynchronizationContext, using current(previous) SynchronizationContext 
    /// and captures culture information from currently running thread 
    /// </summary> 
    public CultureAwareSynchronizationContext() 
     : this(Current) 
    {} 

    /// <summary> 
    /// Uses passed SynchronizationContext (or null, in that case creates new empty SynchronizationContext) 
    /// and captures culture information from currently running thread 
    /// </summary> 
    /// <param name="previous"></param> 
    public CultureAwareSynchronizationContext(SynchronizationContext previous) 
     : this(new ThreadCultureHolder(), previous) 
    { 
    } 

    internal CultureAwareSynchronizationContext(ThreadCultureHolder currentCultureHolder, SynchronizationContext currentSynchronizationContext) 
    { 
     cultureHolder = currentCultureHolder; 
     synchronizationImplementation = currentSynchronizationContext ?? new SynchronizationContext(); 
    } 

    public override void Send(SendOrPostCallback d, object state) 
    { 
     cultureHolder.ApplyCulture(); 
     synchronizationImplementation.Send(d, state); 
    } 

    public override void Post(SendOrPostCallback d, object state) 
    { 
     synchronizationImplementation.Post(passedState => 
     { 
      SetSynchronizationContext(this); 
      cultureHolder.ApplyCulture(); 
      d.Invoke(s); 
     }, state); 
    } 

    public override SynchronizationContext CreateCopy() 
    { 
     return new CultureAwareSynchronizationContext(cultureHolder, synchronizationImplementation.CreateCopy()); 
    } 

    public override string ToString() 
    { 
     return string.Format("CultureAwareSynchronizationContext: {0}", cultureHolder); 
    } 
} 

उपयोग:

/// code that detects Browser's culture 
void Detection() 
{ 
     Thread.CurrentThread.CurrentUICulture = new CultureInfo("cs"); 
     SynchronizationContext.SetSynchronizationContext(new CultureAwareSynchronizationContext()); 
} 

यह समाधान संभव issues mentioned by Hans Passant से पीड़ित है।

+0

यह अभी के लिए सबसे अच्छा समाधान है। ध्यान दें कि कई एएसपी.NET सहायक विधियां हैं जो * मान लीजिए * वर्तमान संदर्भ 'AspNetSynchronizationContext' है। तो इस समाधान का उपयोग देखभाल के साथ किया जाना चाहिए। –

+0

अंतर्निहित एएसपी.NET सिंक संदर्भ को प्रतिस्थापित करने के लिए यह बहुत आक्रामक लगता है। साथ ही, यह धारणाओं को तोड़ सकता है कि ढांचे और पुस्तकालयों ने सिंक संदर्भ क्या किया है या नहीं करता है। सिंक संदर्भ वैश्विक स्थिति है - इसके साथ सावधान रहें। – usr

+0

@ स्टीफन क्लेरी, क्या आप कृपया अधिक विशिष्ट हो सकते हैं? मैंने पाया है कि 'AspNetSynchronizatonContext'' के केवल प्रत्यक्ष संदर्भ पृष्ठ.cs में हैं - उदाहरण के लिए http://referencesource.microsoft.com/#System.Web/UI/Page.cs,5095 - जिनका उपयोग एएसपी की एसिंक लोडिंग के लिए किया जाता है .NET नियंत्रण। मेरा 'सिंक्रनाइज़ेशन कॉन्टेक्स्ट' में समान व्यवहार होना चाहिए जो 'सिंक्रनाइज़ेशन कॉन्टेक्स्ट' था जो पहले उपयोग में था (वर्तमान संस्कृति को छोड़कर) - यह लपेटता है और पिछले को कॉल करता है। – nothrow