كيف يمكن تنفيذ برمجة SerialPort قوية باستخدام .NET / C#؟

StackOverflow https://stackoverflow.com/questions/441559

  •  22-07-2019
  •  | 
  •  

سؤال

أنا أكتب خدمة Windows للتواصل مع قارئ Serial Mag-stripe ولوحة الترحيل (نظام التحكم في الوصول).

أواجه مشكلات حيث يتوقف الكود عن العمل (أحصل على IOExceptions) بعد أن "قاطع" برنامج آخر العملية عن طريق فتح نفس المنفذ التسلسلي مثل الخدمة الخاصة بي.

جزء من الكود كما يلي:

public partial class Service : ServiceBase
{
    Thread threadDoorOpener;
    public Service()
    {
        threadDoorOpener = new Thread(DoorOpener);
    }
    public void DoorOpener()
    {
        while (true)
        {
            SerialPort serialPort = new SerialPort();
            Thread.Sleep(1000);
            string[] ports = SerialPort.GetPortNames();
            serialPort.PortName = "COM1";
            serialPort.BaudRate = 9600;
            serialPort.DataBits = 8;
            serialPort.StopBits = StopBits.One;
            serialPort.Parity = Parity.None;
            if (serialPort.IsOpen) serialPort.Close();
            serialPort.Open();
            serialPort.DtrEnable = true;
            Thread.Sleep(1000);
            serialPort.Close();
        }
    }
    public void DoStart()
    {
        threadDoorOpener.Start();
    }
    public void DoStop()
    {
        threadDoorOpener.Abort();
    }
    protected override void OnStart(string[] args)
    {
        DoStart();
    }
    protected override void OnStop()
    {
        DoStop();
    }
}

يبدأ نموذج البرنامج الخاص بي بنجاح سلسلة العمل، ويؤدي فتح/إغلاق ورفع DTR إلى تشغيل قارئ Mag-stripe الخاص بي (انتظر ثانية واحدة)، وإيقاف التشغيل (انتظر ثانية واحدة) وما إلى ذلك.

إذا قمت بتشغيل HyperTerminal وقمت بالاتصال بنفس منفذ COM، يخبرني HyperTerminal أن المنفذ قيد الاستخدام حاليًا.إذا ضغطت بشكل متكرر على ENTER في HyperTerminal ، لمحاولة إعادة الفتح المنفذ سينجح بعد بضع محاولات.

هذا له تأثير في التسبب في استثناءات IOExceptions في موضوع العمل الخاص بي، وهو أمر متوقع.ومع ذلك، حتى لو قمت بإغلاق HyperTerminal، ما زلت أحصل على نفس IOException في سلسلة أعمالي.العلاج الوحيد هو في الواقع إعادة تشغيل الكمبيوتر.

يبدو أن البرامج الأخرى (التي لا تستخدم مكتبات .NET للوصول إلى المنفذ) تعمل بشكل طبيعي في هذه المرحلة.

أي أفكار حول ما يسبب هذا؟

هل كانت مفيدة؟

المحلول

@thomask

نعم، يقوم Hyperterminal في الواقع بتمكين fAbortOnError في DCB الخاص بـ SetCommState، وهو ما يفسر معظم استثناءات IOExceptions التي يطرحها كائن SerialPort.تحتوي بعض أجهزة الكمبيوتر الشخصية / الأجهزة المحمولة أيضًا على UARTs التي تم تشغيل علامة الإجهاض عند الخطأ افتراضيًا - لذلك من الضروري أن يقوم روتين init الخاص بالمنفذ التسلسلي بمسحها (وهو ما أهملت Microsoft القيام به).لقد كتبت مؤخرًا مقالة طويلة لشرح ذلك بمزيد من التفصيل (انظر هذا إذا كنت مهتما).

نصائح أخرى

ولا يمكنك إغلاق المملوكة لشخص آخر الاتصال إلى منفذ، فإن البرمجية التالية يعمل أبدا:

if (serialPort.IsOpen) serialPort.Close();

ولأن الكائن لم يتم فتح المنفذ الذي لا يمكن إغلاقه.

وأيضا يجب إغلاق والتخلص من المنفذ التسلسلي حتى بعد حدوث استثناءات

try
{
   //do serial port stuff
}
finally
{
   if(serialPort != null)
   {
      if(serialPort.IsOpen)
      {
         serialPort.Close();
      }
      serialPort.Dispose();
   }
}

إذا كنت تريد أن تكون العملية عدم انقطاع ثم يجب أن تحقق مما إذا كان المنفذ مفتوح ومن ثم تتراجع لفترة ثم حاول مرة أخرى، شيء من هذا القبيل.

while(serialPort.IsOpen)
{
   Thread.Sleep(200);
}

هل حاولت مغادرة ميناء مفتوحا في التطبيق الخاص بك، ومجرد تحول DtrEnable على / قبالة، ومن ثم إغلاق المنفذ عند إغلاق التطبيق الخاص بك؟ أي بمعنى:

using (SerialPort serialPort = new SerialPort("COM1", 9600))
{
    serialPort.Open();
    while (true)
    {
        Thread.Sleep(1000);
        serialPort.DtrEnable = true;
        Thread.Sleep(1000);
        serialPort.DtrEnable = false;
    }
    serialPort.Close();
}

وأنا لست على دراية دلالات DTR، لذلك أنا لا أعرف إذا كان ذلك العمل.

تحليل كيفية القيام األوامر المتزامن موثوقة

لا تستخدم وسائل منع، الطبقة الداخلية المساعد لديه بعض الأخطاء الطفيفة.

واستخدام APM مع فئة حالة جلسة العمل، وحالات التي تدير منطقة عازلة والعازلة المؤشر المشتركة عبر المكالمات، وتنفيذ الاستدعاء التي يلتف EndRead في try...catch. في التشغيل العادي، يتم تعيين آخر شيء كتلة try ينبغي القيام به حتى تتداخل المقبل I / O رد مع دعوة BeginRead().

وعندما لا تسير الأمور منحرف، يجب catch الاحتجاج بشكل غير متزامن مندوبة إلى طريقة إعادة تشغيل. يجب الخروج من تنفيذ الاستدعاء فورا بعد كتلة catch ذلك أن منطق إعادة تشغيل يمكن أن تدمر الدورة الحالية (حالة جلسة العمل هو يكاد يكون من المؤكد الفاسدة) وخلق دورة جديدة. يجب أن <م> لا ستنفذ طريقة إعادة تشغيل على الطبقة حالة جلسة العمل لأن هذا من شأنه أن يمنع ذلك من تدمير وإعادة الدورة.

وعندما يتم إغلاق الكائن منفذ تسلسلي (الذي سيحدث عندما يتم إنهاء التطبيق) قد يكون هناك أيضا معلقة عملية الإدخال / الإخراج. عندما يكون هذا هو الحال، إغلاق منفذ تسلسلي سوف تؤدي إلى رد، وتحت هذه الظروف EndRead سوف بطرح استثناء لا يمكن تمييزه من األوامر shitfit عام. يجب تعيين إشارة في حالة جلسة العمل الخاصة بك لمنع سلوك إعادة تشغيل في كتلة catch. هذا سيوقف طريقة إعادة تشغيل الكمبيوتر من التدخل في اغلاق الطبيعي.

ويمكن الاعتماد هذه العمارة على عدم التمسك الكائن منفذ تسلسلي بشكل غير متوقع.

وطريقة إعادة تشغيل يدير إغلاق وإعادة فتح الكائن المنفذ التسلسلي. بعد استدعاء Close() على الكائن SerialPort، اتصل Thread.Sleep(5) لإعطائها فرصة لترك. فمن الممكن لشيء آخر للاستيلاء على الميناء، حتى يكون جاهزا للتعامل مع هذا في حين أن إعادة فتح ذلك.

أعتقد أنني توصلت إلى استنتاج مفاده أن HyperTerminal لا يعمل بشكل جيد.لقد قمت بإجراء الاختبار التالي:

  1. ابدأ خدمتي في "وضع وحدة التحكم"، ويبدأ تشغيل/إيقاف تشغيل الجهاز (أستطيع أن أعرف من خلال مؤشر LED الخاص به).

  2. ابدأ تشغيل HyperTerminal واتصل بالمنفذ.يبقى الجهاز قيد التشغيل (يرفع HyperTerminal DTR) تكتب خدمتي إلى سجل الأحداث ، أنه لا يمكنها فتح المنفذ

  3. أوقف HyperTerminal، وأتحقق من إغلاقه بشكل صحيح باستخدام مدير المهام

  4. يظل الجهاز مغلقًا (قامت HyperTerminal بخفض DTR)، ويستمر تطبيقي في الكتابة إلى سجل الأحداث، قائلًا إنه لا يمكنه فتح المنفذ.

  5. أقوم بتشغيل تطبيق ثالث (التطبيق الذي أحتاج إلى التعايش معه)، وأطلب منه الاتصال بالمنفذ.أنا أفعل ذلك.لا توجد أخطاء هنا.

  6. أقوم بإيقاف التطبيق المذكور أعلاه.

  7. VOILA، بدأت الخدمة مرة أخرى، وتم فتح المنفذ بنجاح، وتم تشغيل/إيقاف مؤشر LED.

ولقد حاولت تغيير الخيط مثل هذا العمل، مع نفس النتيجة بالضبط. مرة واحدة نجحت المحطة الطرفية السريعة مرة واحدة في "الاستيلاء على ميناء" (في حين موضوع لي هو النوم)، وخدمة بلدي لن تكون قادرة على فتح المنفذ مرة أخرى.

public void DoorOpener()
{
    while (true)
    {
        SerialPort serialPort = new SerialPort();
        Thread.Sleep(1000);
        serialPort.PortName = "COM1";
        serialPort.BaudRate = 9600;
        serialPort.DataBits = 8;
        serialPort.StopBits = StopBits.One;
        serialPort.Parity = Parity.None;
        try
        {
            serialPort.Open();
        }
        catch
        {
        }
        if (serialPort.IsOpen)
        {
            serialPort.DtrEnable = true;
            Thread.Sleep(1000);
            serialPort.Close();
        }
        serialPort.Dispose();
    }
}

وهذا الرمز يبدو للعمل بشكل صحيح. لقد اختبرت على آلة المحلية في تطبيق وحدة، وذلك باستخدام PROCOMM زائد لفتح / إغلاق الميناء، ويحافظ البرنامج على موقوتة.

    using (SerialPort port = new SerialPort("COM1", 9600))
    {
        while (true)
        {
            Thread.Sleep(1000);
            try
            {
                Console.Write("Open...");
                port.Open();
                port.DtrEnable = true;
                Thread.Sleep(1000);
                port.Close();
                Console.WriteLine("Close");
            }
            catch
            {
                Console.WriteLine("Error opening serial port");
            }
            finally
            {
                if (port.IsOpen)
                    port.Close();
            }
        }
    }

وهذه الإجابة حصلت على فترة طويلة ليكون تعليق ...

وأعتقد أنه عندما برنامجك في Thread.Sleep (1000) وفتح اتصال المحطة الطرفية الخاص بك، والمحطة الطرفية يأخذ السيطرة على المنفذ التسلسلي. عندما يستيقظ ثم البرنامج الخاص بك ومحاولة لفتح المنفذ التسلسلي، يتم طرح IOException.

وإعادة تصميم طريقة الخاص بك ومحاولة للتعامل مع افتتاح ميناء بطريقة مختلفة.

وتحرير: معلومات عن أن لديك إلى إعادة تشغيل جهاز الكمبيوتر الخاص بك عندما يفشل البرنامج ...

وهذا ربما لأن البرنامج isn't مغلقة حقا، فتح يبرد ومعرفة ما إذا كان يمكنك العثور على خدمة البرنامج. تأكد من إيقاف جميع المواضيع الخاصة بك قبل أن تخرج على التطبيق الخاص بك.

هل هناك سبب وجيه للحفاظ على خدمتكم من "امتلاك" ميناء؟ اتفرج على المدمج في UPS - مرة واحدة كنت اقول انها هناك لUPS المرفقة، مثلا، COM1، يمكنك تقبيل أن داعا الميناء. أنا أقترح أن تفعل الشيء نفسه اذا لم يكن هناك شرط تشغيليا قويا للمشاركة في الميناء.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top