2 votes

Pourquoi le nombre de threads augmente continuellement avec SerialPort.CatchReceivedEvents()

Je suis en train de faire un C# à l'aide de .NET Framework 2.0 pour lire des données à partir de SerialPort pour obtenir le poids d'une balance.

L'application fonctionne bien, fait ce qu'elle est censée faire, mais la le nombre de fils ne cesse d'augmenter et de plus en plus de mémoire est consommée jusqu'à ce que l'application s'arrête, généralement après environ 4 heures.

Lors de l'exécution avec un simulateur de port série, le nombre de threads est stable autour de 30 . Mais quand j'utilise une balance réelle, elle va plus loin que 500 fils .

J'ai utilisé Microsoft Managed Stack Explorer 1.0 pour prendre un vidage des fils et presque tous ont exactement la pile suivante :

0. System.IO.Ports.SerialPort.CatchReceivedEvents (Source Unavailable)
1. System.IO.Ports.SerialStream.EventLoopRunner.CallReceiveEvents (Source Unavailable)
2. System.Threading._ThreadPoolWaitCallback.WaitCallback_Context (Source Unavailable)
3. System.Threading.ExecutionContext.Run (Source Unavailable)
4. System.Threading._ThreadPoolWaitCallback.PerformWaitCallbackInternal (Source Unavailable)
5. System.Threading._ThreadPoolWaitCallback.PerformWaitCallback (Source Unavailable)

Je ne suis pas en mesure d'identifier la raison pour laquelle ces fils sont créés. Quelqu'un a-t-il une idée de ce qui m'échappe ? Merci.

Voici mon code :
Scale.cs -> crée un thread lorsque la méthode open() est appelée. Le thread lit la valeur de getWeight().
Scales.cs -> traite les événements provenant du port série dans la méthode SerialPort_DataReceived(...). C'est là que m_SerialPort.ReadLine() est appelé et finit par fournir la valeur à getWeight().

Scale.cs :

    using System;
    using System.Collections.Generic;
    using System.Threading;
    using ScalesGSS;
    using StateMachine.Exceptions;
    using StateMachine.Log;
    using StateMachine.MessageOutput;

    namespace StateMachine.DriverImplementation
    {

    class Scale : AScale
    {
        private const int Scale_version = 1;

        private Thread thread = null;

        private IScales gScale = null;

        //
        private string m_Type;
        private string m_PortName;
        private int m_BaudRate;
        private char m_Parity;
        private int m_DataBits;
        private string m_StopBits;
        private int m_CommandReturnLength;
        private string m_CommandType;
        private string m_CommandValue;
        private int m_ReadTimeOutInMilliseconds;
        private int m_WeightInitialPosition;
        private int m_WeightFinalPosition;
        private int m_TimeBetweenReadsInMilliseconds;
        private int m_StableReadQuantity;
        private int m_MinimumWeight;
        private int m_ScaleID;
        //
        private double m_OldWeight = 0.0;
        private double m_Offset = 0.0;
        private double m_CurrentWeight = 0.0;
        int m_WeightEqualCount = 0;
        //
        byte m_Status = 3; // "NO COMMUNICATION"
        //
        private bool m_Closed = false;
        private static LogFactory m_Log = new LogFactory(LogCategory.Device, "");
        ErrorDialog m_ErrorDialog = new ErrorDialog();

        public Scale()
        {
            this.setClassName("Scale");
            this.setDeviceType(DeviceType.Scale);
        }

        public void run()
        {
            try
            {

                if (this.m_Type.ToUpper().Equals("GENERICSCALES")) // GENERICSCALES or MOCKSCALES
                    this.gScale = new ScalesGSS.GenericScales();
                else
                    this.gScale = new ScalesGSS.MockScales();

                this.gScale.PortName = this.m_PortName;
                this.gScale.BaudRate = this.m_BaudRate;
                this.gScale.Parity = this.m_Parity.ToString();
                this.gScale.DataBits = this.m_DataBits;
                this.gScale.StopBits = this.m_StopBits;
                this.gScale.CommandReturnLength = this.m_CommandReturnLength;
                this.gScale.CommandType = this.m_CommandType;
                this.gScale.CommandValue = this.m_CommandValue;
                this.gScale.ReadTimeOut = this.m_ReadTimeOutInMilliseconds;
                this.gScale.WeightInitialPosition = this.m_WeightInitialPosition;
                this.gScale.WeightFinalPosition = this.m_WeightFinalPosition;
                this.gScale.setParameters();
                this.gScale.configurePort();

                while (true)
                {
                    if (this.m_Closed)
                    {
                        if (this.OpenedPort())
                            this.gScale.closePort();
                        break;
                    }

                    Thread.Sleep(this.m_TimeBetweenReadsInMilliseconds);

                    if (!this.OpenedPort())
                    {
                        if (!this.OpenPort())
                        {
                            m_Log.writeLogWarning("Error opening serialport.", " Port: " + this.m_PortName, true);
                        }
                    }

                    if (this.ErrorReadingWeight())
                    {
                        m_Log.writeLogWarning("Invalid weight.", " Port: " + this.m_PortName, true);
                    }

                    this.m_CurrentWeight = getWeight();

                    if (!ReadingTimeout())
                    {
                        if (this.m_WeightEqualCount > m_StableReadQuantity)
                        {
                            if (m_CurrentWeight > m_MinimumWeight)
                                m_Status = 2; // "WEIGHT STABLE"
                            else
                            {
                                m_Status = 0; // "SCALE FREE"
                                m_WeightEqualCount = 0;
                            }
                        }
                        else
                        {
                            if (m_CurrentWeight > m_MinimumWeight)
                            {
                                m_Status = 1; // "STABILIZING"

                                if ((this.m_CurrentWeight >= (this.m_OldWeight - this.m_Offset)) && (this.m_CurrentWeight <= (this.m_OldWeight + this.m_Offset)))
                                    this.m_WeightEqualCount++;
                                else
                                    this.m_WeightEqualCount = 0;

                                this.m_OldWeight = this.m_CurrentWeight;
                            }
                            else
                            {
                                m_Status = 0; // "SCALE FREE"
                                m_WeightEqualCount = 0;
                            }
                        }
                    }
                    else
                    {
                        m_WeightEqualCount = 0;
                        m_Status = 3;         // "NO COMMUNICATION"
                        string v_Message = "No communication with scale. Port: " + m_PortName;
                        m_Log.writeLogWarning(v_Message, "", true);
                        AutoClosingMessageBox.Show(v_Message, "Scale", 10000);
                    }
                }
            }
            catch (Exception v_Exception)
            {
                m_Log.writeLogError("run()", v_Exception);
            }
        }

        private bool OpenedPort()
        {
            return this.gScale.OpenedPort;
        }

        private bool OpenPort()
        {
            bool v_OpenPort;
            v_OpenPort = this.gScale.openPort();

            if (!v_OpenPort)
            {
                m_ErrorDialog.getScaleErrorMessage(gScale);
            }

            return v_OpenPort;
        }

        private bool ErrorReadingWeight()
        {
            return this.gScale.ErrorReadingWeight;
        }

        private double getWeight()
        {
            return this.gScale.getWeight();
        }

        private DateTime LastGoodReading()
        {
            return gScale.LastGoodReading;
        }

        private void setLastGoodReading(DateTime p_Value)
        {
            gScale.LastGoodReading = p_Value;
        }

        private bool ReadingTimeout()
        {
            if (m_ReadTimeOutInMilliseconds > 0)
            {
                DateTime v_LastGoodReading = LastGoodReading() == DateTime.MinValue ? DateTime.Now : LastGoodReading();
                setLastGoodReading(DateTime.Now);
                return DateTime.Now > v_LastGoodReading.AddMilliseconds(m_ReadTimeOutInMilliseconds);
            }
            else
                return false;
        }

        #region "IDriverService"

        public override byte getStatus()
        {
            return m_Status;
        }

        public override byte[] read()
        {
            return System.Text.ASCIIEncoding.ASCII.GetBytes(m_CurrentWeight.ToString());
        }

        public override byte[] read(int p_InitialPosition, int p_Size)
        {
            return read();
        }

        public override byte[] write(byte[] p_Data)
        {
            string v_Temp = System.Text.ASCIIEncoding.ASCII.GetString(p_Data);

            if (v_Temp.Equals("getScaleNumber"))
                return System.Text.ASCIIEncoding.ASCII.GetBytes(m_ScaleID.ToString());
            else
                throw new EDriverAccess(1, "Not implemented");
        }

        public override bool open()
        {
            this.thread = new Thread(run);
            this.thread.Name = "SCALE";
            this.thread.IsBackground = true;
            this.thread.Start();
            return true;
        }

        public override bool close()
        {
            try
            {
                this.release();
                return true;
            }
            catch
            {
                return false;
            }
        }

        public override int getVersion()
        {
            return Scale_version;
        }

        public override void setProperties(Dictionary<string, string> p_props)
        {
            try
            {
                this.m_Type = p_props["type"];
                this.m_PortName = p_props["portName"];
                this.m_BaudRate = Int32.Parse(p_props["baudRate"]);
                this.m_Parity = char.Parse(p_props["parity"]);
                this.m_DataBits = Int32.Parse(p_props["dataBits"]);
                this.m_StopBits = p_props["stopBits"];
                this.m_CommandReturnLength = Int32.Parse(p_props["returnLength"]);
                this.m_CommandType = p_props["commandType"];
                this.m_CommandValue = p_props["commandValue"];
                this.m_ReadTimeOutInMilliseconds = Int32.Parse(p_props["readTimeout"]);
                this.m_WeightInitialPosition = Int32.Parse(p_props["weightInitPos"]);
                this.m_WeightFinalPosition = Int32.Parse(p_props["weightFinPos"]);
                this.m_TimeBetweenReadsInMilliseconds = Int32.Parse(p_props["delayLeitura"]);
                this.m_StableReadQuantity = Int32.Parse(p_props["qtdeLeituraEstavel"]);
                this.m_MinimumWeight = Int32.Parse(p_props["pesoMinimo"]);
                this.m_ScaleID = Int32.Parse(p_props["numBalanca"]);
                if (p_props.ContainsKey("precision"))
                    this.m_Offset = Int32.Parse(p_props["precision"]);
            }
            catch (Exception)
            {
                throw new Exception();
            }
        }

        public override void release()
        {
            this.m_Closed = true;
            m_Status = 3; // "NO COMMUNICATION"
        }
        #endregion
    }
}

Scales.cs :

using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Ports;
using System.Reflection;
using System.Timers;
using Scales.Util;

namespace Scales.DLL
{
    public class Scales : Status
    {
        public event EventHandler StableWeightChanged;

        protected virtual void OnCountdownCompleted(EventArgs e)
        {
            if (StableWeightChanged != null)
                StableWeightChanged(this, e);

        }

        System.Timers.Timer timerTimeWithoutSample;
        private int m_IntervalsWithoutSample = 0;
        private string m_EndOfWeightChar = "";

        private void _timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            m_IntervalsWithoutSample++;
        }

        public int IntervalsWithoutSample { get { return m_IntervalsWithoutSample; } }

        private SerialPort m_SerialPort;

        public string PortName { get; set; }
        public int BaudRate { get; set; }
        public int DataBits { get; set; }

        private Double m_Weight = 0;
        public Double Weight
        {
            get
            {
                if (m_BufferWeights.Count > 0)
                {
                    try
                    {
                        m_Weight = treatReceivedValue(m_BufferWeights[m_BufferWeights.Count - 1]);
                    }
                    catch
                    {

                    }
                    finally
                    {
                        ErrorReadingWeight = (m_Weight != -1 ? false : true);
                    }

                }
                else
                {
                    m_Weight = 0;
                }
                return m_Weight;
            }
        }

        public List<Double> getAndFlushPastWeights()
        {
            List<Double> v_FlushedValues = new List<double>();

            Double v_WeightCursor;
            while (m_BufferWeights.Count > 1 && v_FlushedValues.Count < 200)
            {
                v_WeightCursor = treatReceivedValue(m_BufferWeights[0]);

                if (v_WeightCursor >= 0)
                {
                    v_FlushedValues.Add(v_WeightCursor);
                }

                m_BufferWeights.RemoveAt(0);
            }
            return v_FlushedValues;
        }

        public void ResetWeights()
        {
            if (m_BufferWeights != null)
            {
                m_BufferWeights.Clear();
            }
        }

        public string NewLineCommandType { get; set; }
        public string NewLineCommand { get; set; }
        public int ReturnLength { get; set; }
        public int WeightInitialPosition { get; set; }
        public int WeightFinalPosition { get; set; }
        public int MotionBitPos { get; set; }

        public int ReadTimeOut { get; set; }
        public bool OpenedPort { get; private set; }
        public bool ErrorReadingWeight { get; private set; }
        public DateTime LastGoodReading { get; private set; }

        public bool IsStable { get; private set; }

        private Parity PortParity { get; set; }
        public string SerialParity
        {
            get { return PortParity.ToString(); }
            set
            {
                setParity(value);
            }
        }

        public int WeightReadLength
        {
            get
            {
                if (WeightFinalPosition >= WeightInitialPosition)
                {
                    return WeightFinalPosition - WeightInitialPosition + 1;
                }
                else
                {
                    return 0;
                }
            }
        }

        private StopBits PortStopBits { get; set; }
        public string SerialStopBits
        {
            get { return PortStopBits.ToString(); }
            set
            {
                setStopBits(value);
            }
        }

        private void setParity(string p_Parity)
        {
            if (p_Parity.Equals(Parity.Even.ToString()))
            {
                PortParity = Parity.Even;
            }
            else if (p_Parity.Equals(Parity.Mark.ToString()))
            {
                PortParity = Parity.Mark;
            }
            else if (p_Parity.Equals(Parity.Odd.ToString()))
            {
                PortParity = Parity.Odd;
            }
            else if (p_Parity.Equals(Parity.Space.ToString()))
            {
                PortParity = Parity.Space;
            }
            else
            {
                PortParity = Parity.None;
            }
        }

        private void setStopBits(string p_StopBits)
        {
            if (p_StopBits.Equals(StopBits.One.ToString()))
            {
                PortStopBits = StopBits.One;
            }
            else if (p_StopBits.Equals(StopBits.OnePointFive.ToString()))
            {
                PortStopBits = StopBits.OnePointFive;
            }
            else if (p_StopBits.Equals(StopBits.Two.ToString()))
            {
                PortStopBits = StopBits.Two;
            }
            else if (p_StopBits.Equals("1"))
            {
                PortStopBits = StopBits.One;
            }
            else if (p_StopBits.Equals("1.5"))
            {
                PortStopBits = StopBits.OnePointFive;
            }
            else if (p_StopBits.Equals("2"))
            {
                PortStopBits = StopBits.Two;
            }
            else
            {
                PortStopBits = StopBits.None;
            }
        }

        public Scales()
        {
            OpenedPort = false;
            ErrorReadingWeight = false;
            IsStable = false;
            m_IntervalsWithoutSample = 999999;
            timerTimeWithoutSample = new System.Timers.Timer(5);
            timerTimeWithoutSample.Elapsed += new ElapsedEventHandler(_timer_Elapsed);
        }

        private int ignoreNextXValues;
        public void resetScale()
        {
            ErrorReadingWeight = false;
            IsStable = false;
            m_IntervalsWithoutSample = 999999;
            ignoreNextXValues = 2;

            m_BufferWeights.Clear();
            m_BufferTime.Clear();

            if (m_SerialPort != null && m_SerialPort.IsOpen)
            {
                m_SerialPort.Close();
                m_SerialPort.Open();
                m_SerialPort.DiscardInBuffer();
            }

        }

        List<String> m_BufferWeights = new List<String>();
        List<String> m_BufferTime = new List<String>();

        public bool openPort()
        {
            try
            {
                if (m_SerialPort.IsOpen)
                {
                    m_SerialPort.Close();
                }

                m_SerialPort.Open();
                resetScale();

                OpenedPort = true;
                return true;
            }
            catch (Exception ex)
            {
                MessageDetail = ex.Message;
                Return = -100;
                OpenedPort = false;
                return false;
            }
        }

        public bool closePort()
        {
            try
            {
                if (m_SerialPort != null)
                {
                    if (m_SerialPort.IsOpen)
                    {
                        m_SerialPort.Close();
                    }
                }
                OpenedPort = false;

                return true;
            }
            catch (Exception ex)
            {
                MessageDetail = ex.Message;
                Return = -101;
                return false;
            }
        }

        public bool configurePort()
        {
            try
            {
                m_SerialPort = new SerialPort();
                m_SerialPort.PortName = PortName;
                m_SerialPort.BaudRate = BaudRate;
                m_SerialPort.Parity = PortParity;
                m_SerialPort.DataBits = DataBits;
                m_SerialPort.StopBits = PortStopBits;
                m_SerialPort.ReadTimeout = ReadTimeOut > 0 ? ReadTimeOut : SerialPort.InfiniteTimeout;
                m_SerialPort.NewLine = getNewLineCommand();
                m_SerialPort.DataReceived += new SerialDataReceivedEventHandler(SerialPort_DataReceived);

                return true;
            }
            catch (Exception ex)
            {
                MessageDetail = ex.Message;
                Return = -102;
                return false;
            }
        }

        private string getNewLineCommand()
        {
            string v_Command = string.Empty;

            if (NewLineCommandType.ToUpper().Equals(CommandTypes.CHAR.ToUpper()))
            {
                byte v_Char = Convert.ToByte(NewLineCommand);
                v_Command = Convert.ToChar(v_Char).ToString();
            }
            else if (NewLineCommandType.ToUpper().Equals(CommandTypes.STRING.ToUpper()))
            {
                v_Command = NewLineCommand;
            }
            else
            {
                char[] v_delimiters = { '|' };
                String[] v_Strings = NewLineCommand.Split(v_delimiters);

                if (v_Strings.Length == 2)
                {
                    v_Command = v_Strings[0];
                    m_EndOfWeightChar = v_Strings[1];
                }
                else
                {
                    v_Command = NewLineCommand;
                }
            }

            return v_Command;
        }

        private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            try
            {
                LastGoodReading = DateTime.Now;
                string ReadLine = m_SerialPort.ReadLine();
                m_BufferWeights.Add(ReadLine);
            }
            catch (Exception)
            {
                m_Weight = 0;
                LastGoodReading = DateTime.MinValue;
            }
        }

        private Double treatReceivedValue(string p_ReceivedValue)
        {
            try
            {
                if (ignoreNextXValues > 0) ignoreNextXValues--;
                if (ignoreNextXValues > 0) return 0;

                double v_Value = double.MinValue;
                p_ReceivedValue = p_ReceivedValue.Replace("\r", "").Replace("\n", "");

                m_IntervalsWithoutSample = 0;

                if (p_ReceivedValue.Length < WeightInitialPosition + WeightReadLength)
                {
                    return -1;
                }
                if (MotionBitPos != -1 && p_ReceivedValue.Length < MotionBitPos - 1)
                {
                    return -1;
                }

                string strValor = "";

                if (NewLineCommandType.ToUpper().Equals(CommandTypes.VARIABLE_LENGTH.ToUpper()))
                {
                    int v_EndCharPos = p_ReceivedValue.IndexOf(m_EndOfWeightChar);

                    if (v_EndCharPos != -1)
                    {
                        strValor = p_ReceivedValue.Substring(0, v_EndCharPos).Trim();
                    }
                }
                else
                {
                    strValor = p_ReceivedValue.Substring(WeightInitialPosition, WeightReadLength).Trim();
                }

                bool IsDouble = double.TryParse(strValor, out v_Value);

                if (IsDouble)
                {
                    if (MotionBitPos != -1)
                    {
                        string bit = p_ReceivedValue.Substring(MotionBitPos, 1).Trim();
                        if (bit == "1")
                        {
                            IsStable = true;
                        }
                        else IsStable = false;
                    }
                    else
                    {
                        IsStable = true;
                    }

                    return v_Value;
                }
                else
                {
                    return -1;
                }
            }
            catch (Exception ex)
            {
                Return = -200;
                MessageDetail = ex.Message + " - Fonte:readScales";
                ErrorReadingWeight = true;
            }
            return -1;
        }
    }
}

2voto

Pete Baughman Points 1679

Vous devriez essayer de réduire votre code problématique à quelque chose de plus gérable, car cela facilitera le débogage pour les autres. Il y a beaucoup de logique d'application dans ce code qui n'est probablement pas pertinente pour le problème, ce qui peut rendre difficile pour les gens de voir ce qui se passe. Vous obtiendrez beaucoup plus de réponses si votre exemple est plus court. Vous pourrez même résoudre le problème vous-même !

Cela dit J'ai une intuition sur ce qui ne va pas, mais vous devrez faire un peu de travail vous-même pour découvrir si j'ai raison ou non :

Le port série .NET fonctionne en attendant l'arrivée de données, puis en déclenchant l'événement DataReceived sur un thread de travail dès qu'il constate l'arrivée de nouvelles données. Je crois que vous avez 400 ou 500 de ces fils de travail qui ne terminent jamais leur travail. pour qu'ils ne disparaissent jamais.

Votre gestionnaire d'événement pour l'événement SerialPort.DataReceived semble bloquer en attendant l'arrivée d'une ligne entière, mais l'événement sera déclenché chaque fois qu'il y aura une certaine quantité de nouvelles données sur le port série (pas nécessairement une ligne entière). Si une longue ligne de texte arrive, l'événement DataReceived va se déclencher plusieurs fois, chacun sur son propre thread. Ces threads sont synchronisés les uns avec les autres, donc ils vont tous attendre que le précédent se termine.

  1. Le premier thread qui est mis en file d'attente va attendre un moment à m_SerialPort.ReadLine() jusqu'à ce que la ligne entière arrive.
  2. Plusieurs fils de discussion se mettent en place derrière le premier fil au fur et à mesure que d'autres personnages arrivent. Le reste des threads finira par attendre que le premier thread ait fini d'exécuter votre gestionnaire d'événements.
  3. Enfin, la ligne entière entre en scène. Le premier thread se termine, et l'un des 5 ou 6 qui sont en file d'attente derrière lui s'exécute et le processus recommence.
  4. Le thread en cours bloque sur ReadLine, 5 ou 6 autres font la queue derrière lui. (Nous sommes maintenant de retour à 1)

Au bout d'un moment, vous avez tellement de fils en attente que vous rencontrez des problèmes de mémoire.

Vous avez probablement réglé le délai de lecture de m_SerialPort sur timeout.Infinite. Si vous réglez le délai d'attente sur quelque chose de plus petit, comme 1 seconde (1000), et que vous obtenez beaucoup d'exceptions TimeoutExceptions dans votre fichier SerialPort_DataReceived méthode, alors j'ai probablement raison

Note complémentaire Vous devez attraper un type d'exception plus spécifique dans votre gestionnaire d'événement DataReceived. La capture d'une exception peut masquer exactement ce type de problème.

Si j'ai correctement diagnostiqué le problème, vous devrez modifier un peu l'architecture de votre programme. La chose la plus simple à faire est de ne pas s'abonner à l'événement DataReceived et d'avoir un événement de type simple Le fil de travail appelle m_SerialPort.ReadLine() ; avec un délai d'attente infini. Lorsqu'il lit une ligne, faites en sorte que ce fil d'exécution déclenche un événement avec la ligne entière de texte reçue et abonnez-vous à CET événement au lieu de l'événement SerialPort.DataReceived() ;.

Sinon, si vous souhaitez vous abonner à l'événement SerialPort.DataReceived() ;, lisez les caractères individuels à partir du SerialPort jusqu'à ce que SerialPort.BytesToRead soit égal à zéro et placez-les dans un tampon. Ensuite, lorsque vous avez une ligne entière, vous pouvez déclencher un événement "LineReceived" que vous créez vous-même et qui renvoie la ligne entière en une seule fois comme l'un des EventArgs. Cette méthode ne nécessite pas que vous fassiez tourner votre propre thread qui persiste pendant un temps très long.

2voto

Martin Blaustein Points 610

J'ai eu un problème similaire, en utilisant SerialPort.ReadExisting() au lieu de SerialPort.ReadLine() J'ai pu éviter la création de fils infinis.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X