2014-11-28 10 views
9

मुझे एक यूनिट परीक्षण के साथ एक समस्या आई है जो असफल रहा क्योंकि एक टीपीएल टास्क ने कभी भी ContinueWith(x, TaskScheduler.FromCurrentSynchronizationContext()) निष्पादित नहीं किया।टास्क.कॉन्टीन्यू क्यों इस यूनिट टेस्ट में निष्पादित करने में विफल रहता है?

समस्या यह साबित हुई क्योंकि कार्य शुरू होने से पहले Winforms UI नियंत्रण गलती से बनाया जा रहा था।

यहां एक उदाहरण है जो इसे पुन: उत्पन्न करता है। आप देखेंगे कि यदि आप परीक्षण को चलाते हैं, तो यह गुजरता है। यदि आप फॉर्म लाइन के साथ परीक्षण को असम्बद्ध करते हैं, तो यह विफल हो जाता है।

[TestClass] 
public class UnitTest1 
{ 
    [TestMethod] 
    public void TestMethod1() 
    { 
     // Create new sync context for unit test 
     SynchronizationContext.SetSynchronizationContext(new SynchronizationContext()); 

     var waitHandle = new ManualResetEvent(false); 

     var doer = new DoSomethinger(); 

     //Uncommenting this line causes the ContinueWith part of the Task 
     //below never to execute. 
     //var f = new Form(); 

     doer.DoSomethingAsync(() => waitHandle.Set()); 

     Assert.IsTrue(waitHandle.WaitOne(10000), "Wait timeout exceeded."); 
    } 
} 


public class DoSomethinger 
{ 
    public void DoSomethingAsync(Action onCompleted) 
    { 
     var task = Task.Factory.StartNew(() => Thread.Sleep(1000)); 

     task.ContinueWith(t => 
     { 
      if (onCompleted != null) 
       onCompleted(); 

     }, TaskScheduler.FromCurrentSynchronizationContext()); 
    } 
} 

क्या कोई यह समझा सकता है कि ऐसा क्यों है?

मैंने सोचा था कि क्योंकि गलतSynchronizationContext प्रयोग किया जाता है यह हो सकता है, लेकिन वास्तव में, ContinueWithकभी नहीं सब पर निष्पादित करता है! और इसके अलावा, इस यूनिट परीक्षण में, चाहे वह सही है SynchronizationContext अप्रासंगिक है क्योंकि जब तक waitHandle.set() किसी भी थ्रेड पर कॉल किया जाता है, तो परीक्षण पास होना चाहिए।

+0

क्या कोई अपवाद होता है? –

+0

@ श्रीराम सक्थिवेल बेवकूफ सवाल - आपने 'var f = new form();' uncommented 'के साथ प्रयास किया था? मैं एक एमएसटेस्ट, वीएस2013u4, विन 8.1, नेट 4.5.1 – OffHeGoes

+0

@ ओफहेगोस क्षमा करें मैंने इसे अनदेखा किया। अब एक उत्तर लिख रहा है :) –

उत्तर

7

मैं अपने कोड में टिप्पणी अनुभाग, वास्तव में यह है कि जब uncommenting var f = new Form();

कारण सूक्ष्म है विफल रहता है अनदेखी, Control वर्ग स्वचालित रूप से अगर यह देखता है कि SynchronizationContext.Currentnull या प्रकार की अपनी है WindowsFormsSynchronizationContext को तुल्यकालन संदर्भ के ऊपर लिख देगा System.Threading.SynchronizationContext

जैसे ही नियंत्रण वर्ग Send और Post को SynchronizationContext.CurrentWindowsFormsSynchronizationContext साथ, सभी कॉल्स के ऊपर लिख के रूप में की उम्मीद है खिड़कियों संदेश पाश काम करने के लिए चल रहा हो। ऐसा तब तक नहीं होगा जब तक आप Handle नहीं बनाते और आप एक संदेश लूप चलाते हैं। समस्याग्रस्त कोड की

प्रासंगिक हिस्सा:

internal Control(bool autoInstallSyncContext) 
{ 
    ... 
    if (autoInstallSyncContext) 
    { 
     //This overwrites your SynchronizationContext 
     WindowsFormsSynchronizationContext.InstallIfNeeded(); 
    } 
} 

आप WindowsFormsSynchronizationContext.InstallIfNeededhere का स्रोत देख सकते हैं।

यदि आप SynchronizationContext को ओवरराइट करना चाहते हैं, तो आपको इसे काम करने के लिए SynchronizationContext के अपने कस्टम कार्यान्वयन की आवश्यकता है।

वर्कअराउंड:।

internal class MyContext : SynchronizationContext 
{ 

} 

[TestMethod] 
public void TestMethod1() 
{ 
    // Create new sync context for unit test 
    SynchronizationContext.SetSynchronizationContext(new MyContext()); 

    var waitHandle = new ManualResetEvent(false); 

    var doer = new DoSomethinger(); 
    var f = new Form(); 

    doer.DoSomethingAsync(() => waitHandle.Set()); 

    Assert.IsTrue(waitHandle.WaitOne(10000), "Wait timeout exceeded."); 
} 

कोड से ऊपर काम करता है के रूप में उम्मीद :)

वैकल्पिक रूप से आप false करने के लिए WindowsFormsSynchronizationContext.AutoInstall सेट कर सकते हैं, कि तुल्यकालन संदर्भ ऊपर उल्लेख का स्वत: ओवरराइटिंग कर पाएगा (ओपी @OffHeGoes के लिए धन्यवाद टिप्पणियों में इसका उल्लेख करने के लिए)

+0

आह, मुझे लगता है कि मुझे पता है कि मैंने क्या गलत किया - समस्या को डीबग करने का प्रयास करते समय मैं 'टास्कशेड्यूलर। कंटेंट' की जांच कर रहा था - मुझे 'सिंक्रनाइज़ेशन कॉन्टेक्स्ट.क्यूरेंट' की जांच करनी चाहिए! – OffHeGoes

+0

यह जानना वास्तव में उपयोगी है। मैंने अभी 'WindowsFormsSynchronizationContext.InstallIfNeeded() 'को देखा है और देख सकता है कि यह' टाइपऑफ (सिंक्रनाइज़ेशन कॉन्टेक्स्ट)' के लिए जांचता है। – OffHeGoes

+1

यह भी ध्यान दिया गया है कि आप इसे रोकने के लिए भी ऐसा कर सकते हैं: 'WindowsFormsSynchronizationContext.AutoInstall = false; ' – OffHeGoes

3

लाइन के साथ टिप्पणी की गई, आपके SynchronizationContext आपके द्वारा बनाए गए डिफ़ॉल्ट हैं। यह डिफ़ॉल्ट शेड्यूलर का उपयोग करने के लिए TaskScheduler.FromCurrentSynchrozisationContext() का कारण बन जाएगा, जो थ्रेड पूल पर निरंतरता को चलाएगा।

एक बार जब आप बनाने के एक Winforms अपने फार्म की तरह वस्तु, वर्तमान SynchronizationContext एक WindowsFormsSynchronizationContext, जो बारी में एक अनुसूचक कि WinForms संदेश पंप पर निर्भर करता है निरंतरता शेड्यूल करने के लिए वापस आ जाएगी हो जाता है।

चूंकि यूनिट परीक्षण में कोई WinForms पंप नहीं है, इसलिए निरंतरता कभी नहीं चलती है।

+0

धन्यवाद - समस्या को डीबग करने का प्रयास करते समय मैं 'TaskScheduler.Current' की जांच कर रहा था - मुझे 'सिंक्रनाइज़ेशन कॉन्टेक्स्ट.क्यूरेंट' की जांच करनी चाहिए थी। – OffHeGoes

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