2015-08-16 8 views
5

पृष्ठभूमि

एक ग्राहक ने मुझे यह जानने के लिए कहा कि क्यों उनके सी # एप्लिकेशन (हम इसे सलाह देंगे जो एक सलाहकार द्वारा डिलीवर किया गया है) जो इतनी चंचल है, और इसे ठीक करें। एप्लिकेशन एक सीरियल कनेक्शन पर माप उपकरण को नियंत्रित करता है। कभी-कभी डिवाइस निरंतर रीडिंग (जो स्क्रीन पर प्रदर्शित होते हैं) प्रदान करता है, और कभी-कभी ऐप को निरंतर माप को रोकने और कमांड-प्रतिक्रिया मोड में जाने की आवश्यकता होती है।सी # - धारावाहिक बंदरगाह से कौन सा धागा पढ़ता है?

कैसे नहीं यह

निरंतर माप के लिए ऐसा करने के लिए, XXX धारावाहिक इनपुट की पृष्ठभूमि प्रसंस्करण के लिए System.Timers.Timer उपयोग करता है। जब टाइमर आग लगती है, सी # टाइमर के ElapsedEventHandler को अपने पूल से कुछ धागे का उपयोग करके चलाता है। XXX का इवेंट हैंडलर कई दूसरे टाइमआउट के साथ commPort.ReadLine() को अवरुद्ध करता है, फिर सीरियल पोर्ट पर एक उपयोगी माप आने पर प्रतिनिधि को वापस कॉल करता है। यह हिस्सा ठीक काम करता है, हालांकि ...

जब रीयलटाइम माप को रोकने का समय होता है और डिवाइस को कुछ अलग करने का आदेश देता है, तो एप्लिकेशन टाइमर के Enabled = false को सेट करके जीयूआई थ्रेड से पृष्ठभूमि प्रोसेसिंग को निलंबित करने का प्रयास करता है। बेशक, यह सिर्फ आगे की घटनाओं को रोकने के झंडे को सेट करता है, और धारावाहिक इनपुट के लिए पहले से ही इंतजार कर रहे पृष्ठभूमि थ्रेड का इंतजार जारी है। जीयूआई थ्रेड तब डिवाइस को एक कमांड भेजता है, और उत्तर पढ़ने की कोशिश करता है - लेकिन पृष्ठभूमि थ्रेड द्वारा जवाब प्राप्त होता है। अब पृष्ठभूमि धागा उलझन में आता है क्योंकि यह अपेक्षित माप नहीं है। इस बीच जीयूआई थ्रेड भ्रमित हो जाता है क्योंकि इसे अपेक्षित कमांड उत्तर प्राप्त नहीं हुआ था। अब हम जानते हैं कि क्यों XXX इतना तेज है।

संभव विधि 1

एक और इसी तरह आवेदन में, मैं के लिए मुफ्त समय से चल रहे माप एक System.ComponentModel.BackgroundWorker धागा इस्तेमाल किया।

  1. कॉल धागे पर CancelAsync विधि, और
  2. कॉल commPort.DiscardInBuffer() है, जो एक लंबित (अवरुद्ध, प्रतीक्षा में) का कारण बनता है के लिए पृष्ठभूमि सूत्र में पढ़ा अच्छी तरह पेश आना: पृष्ठभूमि प्रसंस्करण निलंबित करने के लिए मैं जीयूआई सूत्र में दो बातें किया System.IO.IOException "The I/O operation has been aborted because of either a thread exit or an application request.\r\n" फेंक दें।

पृष्ठभूमि धागे में मैं इस अपवाद को पकड़ता हूं और तत्काल साफ करता हूं, और सभी कार्य इरादे से साफ करते हैं। दुर्भाग्यवश DiscardInBuffer किसी अन्य थ्रेड के अवरोधन में अपवाद को उत्तेजित करने के लिए कहीं भी दस्तावेज व्यवहार नहीं है, और मुझे अनियंत्रित व्यवहार पर भरोसा करने से नफरत है। यह काम करता है क्योंकि आंतरिक रूप से DiscardInBuffer Win32 API PurgeComm को कॉल करता है, जो अवरुद्ध पढ़ने (दस्तावेज व्यवहार) को बाधित करता है।

संभव विधि 2

सीधे, एक मॉनिटर रद्द टोकन के माध्यम से, BaseClass Stream.ReadAsync विधि का उपयोग पृष्ठभूमि आईओ दखल के समर्थित तरह से इस्तेमाल करते हैं।

क्योंकि प्राप्त होने वाले पात्रों की संख्या परिवर्तनीय (एक नई लाइन द्वारा समाप्त) है, और फ्रेमवर्क में ReadAsyncLine विधि मौजूद नहीं है, मुझे नहीं पता कि यह संभव है या नहीं। मैं प्रत्येक चरित्र को व्यक्तिगत रूप से संसाधित कर सकता हूं लेकिन एक प्रदर्शन हिट लेगा (धीमी मशीनों पर काम नहीं कर सकता है, बशर्ते कि लाइन-टर्मिनेशन बिट पहले ही फ्रेमवर्क के भीतर सी # में कार्यान्वित नहीं किया गया हो)।

संभव विधि 3

एक ताला बनाएँ "मैं सीरियल पोर्ट मिल गया है"। बंदरगाह से इनपुट को पढ़ता, लिखता या छोड़ता नहीं है जब तक कि उनके पास लॉक न हो (पृष्ठभूमि थ्रेड में अवरुद्ध अवरोध को दोहराएं)। बहुत अधिक ओवरहेड के बिना स्वीकार्य जीयूआई प्रतिक्रिया के लिए पृष्ठभूमि थ्रेड में टाइमआउट मानों को 1/4 सेकेंड तक चिपकाएं।

प्रश्न

किसी को भी इस समस्या से निपटने के लिए एक सिद्ध समाधान है? धारावाहिक बंदरगाह की पृष्ठभूमि प्रक्रिया को साफ तरीके से कैसे रोक सकता है? मैंने गुमराह किया है और सी # SerialPort कक्षा को झुकाव वाले लेखों के दर्जनों लेख पढ़े हैं, लेकिन उन्हें कोई अच्छा समाधान नहीं मिला है।

अग्रिम धन्यवाद! SerialPort कक्षा के लिए

+0

आप वास्तविक समस्या पर ध्यान केंद्रित नहीं कर रहे हैं, यह System.Timers.Timer है। इससे छुटकारा पाएं और इसके बजाय एक सिंक्रोनस टाइमर का उपयोग करें। –

+0

क्षमा करें हंस, मैं पालन नहीं करता हूं। संभव विधियों में से कोई भी नहीं 1-3 सिस्टम का उपयोग करें। टिमर्स। टिमर; आप क्या सुझाव दे रहे हैं –

उत्तर

2

MSDN लेख स्पष्ट रूप से कहा गया है: एक SerialPort वस्तु पढ़ने आपरेशन के दौरान अवरुद्ध हो

हैं, तो धागा गर्भपात नहीं है। इसके बजाय, बेस स्ट्रीम या SerialPort ऑब्जेक्ट का निपटान बंद करें।

तो मेरे दृष्टिकोण से सबसे अच्छा तरीका, async लाइन-एंडिंग चरित्र के लिए चरण-दर-चरण जांच के साथ दूसरा चरण है। जैसा कि आपने कहा है, प्रत्येक चार के लिए चेक बहुत बड़ा प्रदर्शन हानि है, मैं सुझाव देता हूं कि आप कुछ विचारों के लिए ReadLine implementation की जांच करने के लिए सुझाव दें कि यह तेज़ी से कैसे करें। ध्यान दें कि वे SerialPort कक्षा की संपत्ति का उपयोग करते हैं।

डिफ़ॉल्ट रूप से, ReadLine विधि जब तक एक लाइन प्राप्त होता है को अवरुद्ध कर देगा:

मैं नोट करने के लिए है कि वहाँ कोई ReadLineAsync विधि डिफ़ॉल्ट as the MSDN states से भी चाहते हैं। यदि यह व्यवहार अवांछनीय है, तो ReadTimeout प्रॉपर्टी को किसी भी गैर-शून्य मान पर ReadLine विधि को TimeoutException को फेंकने के लिए सेट करें ताकि पोर्ट पर कोई लाइन उपलब्ध न हो।

तो, हो सकता है, अपने आवरण में आप समान तर्क लागू कर सकते हैं, अगर वहाँ कुछ निश्चित समय में कोई लाइन अंत है तो अपने Task रद्द हो जाएगा। इसके अलावा, आप इस पर ध्यान देना चाहिए: वर्ग बफ़र्स डेटा

SerialPort क्योंकि

, और धारा BaseStream संपत्ति में शामिल नहीं, दो के बारे में कैसे कई बाइट पढ़ने के लिए उपलब्ध हैं प्रतिकूल हो सकती है। BytesToRead संपत्ति संकेत कर सकते हैं वहाँ पढ़ने के लिए बाइट्स हैं कि, लेकिन इन बाइट्स धारा BaseStream संपत्ति क्योंकि वे SerialPort वर्ग के लिए बफ़र कर दिया है में निहित के लिए सुलभ नहीं हो सकता है।

तो, फिर से, मैं तुम्हें अतुल्यकालिक पढ़ने और प्रत्येक पढ़ने के बाद जाँच के साथ कुछ आवरण तर्क लागू करने के लिए, वहाँ लाइन के अंत या नहीं, जो अवरुद्ध किया जाना चाहिए रहे हैं, और async विधि के अंदर लपेट, जो रद्द हो जाएगा का सुझाव कुछ समय के बाद Task

उम्मीद है कि इससे मदद मिलती है।

0

ठीक है, मैंने यह किया है ... टिप्पणियों की सराहना की जाएगी क्योंकि सी # अभी भी मेरे लिए कुछ नया है!

सीरियल पोर्ट को समवर्ती रूप से (या किसी भी संसाधन, विशेष रूप से एक असीमित संसाधन) तक पहुंचने का प्रयास करने वाले कई धागे होने के लिए यह पागल है।

  • जीयूआई धागा जब यह एक पृष्ठभूमि आपरेशन चल रहा है को छोड़कर SerialPortLockObject रखती है: एक बार फिर से लिखने के बिना इस आवेदन अप ठीक करने के लिए, मैं इस प्रकार विशेष सीरियल पोर्ट पहुंच की गारंटी करने के लिए एक ताला SerialPortLockObject की शुरुआत की।
  • SerialPort कक्षा लपेटा गया है ताकि SerialPortLockObject धारण न किए गए थ्रेड द्वारा कोई भी पढ़ा या लिखना अपवाद फेंकता है (कई विवादों की खोज में मदद मिली)।
  • टाइमर क्लास लपेटा गया है (वर्ग SerialOperationTimer) ताकि पृष्ठभूमि कार्यकर्ता फ़ंक्शन को SerialPortLockObject प्राप्त करके ब्रैकेट किया जाता है। SerialOperationTimer एक समय में केवल एक टाइमर चल रहा है (कई बग खोजने में मदद मिली जहां जीयूआई एक अलग टाइमर शुरू करने से पहले पृष्ठभूमि प्रसंस्करण को रोकने के लिए भूल गया)। टाइमर काम के लिए एक विशिष्ट थ्रेड का उपयोग करके इसे बेहतर किया जा सकता है, उस थ्रेड के साथ टाइमर सक्रिय होने पर पूरे समय लॉक धारण करता है (लेकिन यह अभी भी अधिक काम करेगा; कोडथ्रेड पूल से वर्कर फ़ंक्शन चलाता है)।
  • जब कोई सीरियलऑपरेशन टाइमर बंद हो जाता है, तो यह अंतर्निहित टाइमर को अक्षम करता है और सीरियल पोर्ट बफर को फहराता है (उपरोक्त संभावित विधि 1 में समझाया गया है, जैसा कि किसी अवरुद्ध सीरियल पोर्ट ऑपरेशन से अपवाद को उत्तेजित करता है)। फिर SerialPortLockObject जीयूआई थ्रेड द्वारा पुनः प्राप्त किया जाता है।

यहाँ SerialPort के लिए आवरण है:

/// <summary> CheckedSerialPort class checks that read and write operations are only performed by the thread owning the lock on the serial port </summary> 
// Just check reads and writes (not basic properties, opening/closing, or buffer discards). 
public class CheckedSerialPort : SafePort /* derived in turn from SerialPort */ 
{ 
    private void checkOwnership() 
    { 
     try 
     { 
      if (Monitor.IsEntered(XXX_Conn.SerialPortLockObject)) return; // the thread running this code has the lock; all set! 
      // Ooops... 
      throw new Exception("Serial IO attempted without lock ownership"); 
     } 
     catch (Exception ex) 
     { 
      StringBuilder sb = new StringBuilder(""); 
      sb.AppendFormat("Message: {0}\n", ex.Message); 
      sb.AppendFormat("Exception Type: {0}\n", ex.GetType().FullName); 
      sb.AppendFormat("Source: {0}\n", ex.Source); 
      sb.AppendFormat("StackTrace: {0}\n", ex.StackTrace); 
      sb.AppendFormat("TargetSite: {0}", ex.TargetSite); 
      Console.Write(sb.ToString()); 
      Debug.Assert(false); // lets have a look in the debugger NOW... 
      throw; 
     } 
    } 
    public new int ReadByte()          { checkOwnership(); return base.ReadByte(); } 
    public new string ReadTo(string value)       { checkOwnership(); return base.ReadTo(value); } 
    public new string ReadExisting()        { checkOwnership(); return base.ReadExisting(); } 
    public new void Write(string text)        { checkOwnership(); base.Write(text); } 
    public new void WriteLine(string text)       { checkOwnership(); base.WriteLine(text); } 
    public new void Write(byte[] buffer, int offset, int count)  { checkOwnership(); base.Write(buffer, offset, count); } 
    public new void Write(char[] buffer, int offset, int count)  { checkOwnership(); base.Write(buffer, offset, count); } 
} 

और यहाँ System.Timers.Timer के लिए आवरण है:

/// <summary> Wrap System.Timers.Timer class to provide safer exclusive access to serial port </summary> 
class SerialOperationTimer 
{ 
    private static SerialOperationTimer runningTimer = null; // there should only be one! 
    private string name; // for diagnostics 
    // Delegate TYPE for user's callback function (user callback function to make async measurements) 
    public delegate void SerialOperationTimerWorkerFunc_T(object source, System.Timers.ElapsedEventArgs e); 
    private SerialOperationTimerWorkerFunc_T workerFunc; // application function to call for this timer 
    private System.Timers.Timer timer; 
    private object workerEnteredLock = new object(); 
    private bool workerAlreadyEntered = false; 

    public SerialOperationTimer(string _name, int msecDelay, SerialOperationTimerWorkerFunc_T func) 
    { 
     name = _name; 
     workerFunc = func; 
     timer = new System.Timers.Timer(msecDelay); 
     timer.Elapsed += new System.Timers.ElapsedEventHandler(SerialOperationTimer_Tick); 
    } 

    private void SerialOperationTimer_Tick(object source, System.Timers.ElapsedEventArgs eventArgs) 
    { 
     lock (workerEnteredLock) 
     { 
      if (workerAlreadyEntered) return; // don't launch multiple copies of worker if timer set too fast; just ignore this tick 
      workerAlreadyEntered = true; 
     } 
     bool lockTaken = false; 
     try 
     { 
      // Acquire the serial lock prior calling the worker 
      Monitor.TryEnter(XXX_Conn.SerialPortLockObject, ref lockTaken); 
      if (!lockTaken) 
       throw new System.Exception("SerialOperationTimer " + name + ": Failed to get serial lock"); 
      // Debug.WriteLine("SerialOperationTimer " + name + ": Got serial lock"); 
      workerFunc(source, eventArgs); 
     } 
     finally 
     { 
      // release serial lock 
      if (lockTaken) 
      { 
       Monitor.Exit(XXX_Conn.SerialPortLockObject); 
       // Debug.WriteLine("SerialOperationTimer " + name + ": released serial lock"); 
      } 
      workerAlreadyEntered = false; 
     } 
    } 

    public void Start() 
    { 
     Debug.Assert(Form1.GUIthreadHashcode == Thread.CurrentThread.GetHashCode()); // should ONLY be called from GUI thread 
     Debug.Assert(!timer.Enabled); // successive Start or Stop calls are BAD 
     Debug.WriteLine("SerialOperationTimer " + name + ": Start"); 
     if (runningTimer != null) 
     { 
      Debug.Assert(false); // Lets have a look in the debugger NOW 
      throw new System.Exception("SerialOperationTimer " + name + ": Attempted 'Start' while " + runningTimer.name + " is still running"); 
     } 
     // Start background processing 
     // Release GUI thread's lock on the serial port, so background thread can grab it 
     Monitor.Exit(XXX_Conn.SerialPortLockObject); 
     runningTimer = this; 
     timer.Enabled = true; 
    } 

    public void Stop() 
    { 
     Debug.Assert(Form1.GUIthreadHashcode == Thread.CurrentThread.GetHashCode()); // should ONLY be called from GUI thread 
     Debug.Assert(timer.Enabled); // successive Start or Stop calls are BAD 
     Debug.WriteLine("SerialOperationTimer " + name + ": Stop"); 

     if (runningTimer != this) 
     { 
      Debug.Assert(false); // Lets have a look in the debugger NOW 
      throw new System.Exception("SerialOperationTimer " + name + ": Attempted 'Stop' while not running"); 
     } 
     // Stop further background processing from being initiated, 
     timer.Enabled = false; // but, background processing may still be in progress from the last timer tick... 
     runningTimer = null; 
     // Purge serial input and output buffers. Clearing input buf causes any blocking read in progress in background thread to throw 
     // System.IO.IOException "The I/O operation has been aborted because of either a thread exit or an application request.\r\n" 
     if(Form1.xxConnection.PortIsOpen) Form1.xxConnection.CiCommDiscardBothBuffers(); 
     bool lockTaken = false; 
     // Now, GUI thread needs the lock back. 
     // 3 sec REALLY should be enough time for background thread to cleanup and release the lock: 
     Monitor.TryEnter(XXX_Conn.SerialPortLockObject, 3000, ref lockTaken); 
     if (!lockTaken) 
      throw new Exception("Serial port lock not yet released by background timer thread "+name); 
     if (Form1.xxConnection.PortIsOpen) 
     { 
      // Its possible there's still stuff in transit from device (for example, background thread just completed 
      // sending an ACQ command as it was stopped). So, sync up with the device... 
      int r = Form1.xxConnection.CiSync(); 
      Debug.Assert(r == XXX_Conn.CI_OK); 
      if (r != XXX_Conn.CI_OK) 
       throw new Exception("Cannot re-sync with device after disabling timer thread " + name); 
     } 
    } 

    /// <summary> SerialOperationTimer.StopAllBackgroundTimers() - Stop all background activity </summary> 
    public static void StopAllBackgroundTimers() 
    { 
     if (runningTimer != null) runningTimer.Stop(); 
    } 

    public double Interval 
    { 
     get { return timer.Interval; } 
     set { timer.Interval = value; } 
    } 

} // class SerialOperationTimer 
+0

आपके पास सही विचार है, लेकिन जिस वस्तु को आप सार्वजनिक रूप से लॉक कर रहे हैं उसे एक विरोधी पैटर्न माना जाता है और अच्छे कारण के लिए (क्योंकि कोई भी कोड मॉनीटर को हाइजैक कर सकता है)। इसके बजाय, मैं एक कस्टम सेफपोर्ट प्रकार बनाउंगा जो थ्रेड सुरक्षित है और इसे अपने लॉकिंग का प्रदर्शन करता है, और उस सुरक्षित पोर्ट प्रकार (सभी लॉकिंग सामान निजी के साथ) के सार्वजनिक तरीकों का उपयोग करने के लिए नग्न बंदरगाह तक पहुंच को मजबूर करने के लिए सभी कोड को दोबारा दोहराता है। बहुत साफ करने वाला, और मॉनीटर तक पहुंच को सत्यापित करने की कोई आवश्यकता नहीं है जो बहुत पीछे की ओर दिखती है, आदि। सी # और .NET में थ्रेडिंग प्राइमेटिव्स पर अधिक जानकारी के लिए, इस उत्कृष्ट संसाधन को देखें: www.albahari.com/threading/ – Mahol25

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