Как синхронизировать между прослушивающим / отправляющим потоком tcp-клиента и основным выполнением?

#c# #multithreading #sync

#c# #многопоточность #синхронизация

Вопрос:

у меня есть простая служба Windows, которая запускается и запускает поток, который прослушивает / получает сердцебиение через tcp / ip. мне трудно найти способы синхронизации между получением информации из потока tcp и использованием этого значения для обновления чего-либо в основном потоке.

я пытаюсь использовать метод thread.sleep и продолжаю повторять его несколько раз, ожидая ответа от потока, а затем получая значение, но этот метод кажется немного изменчивым, поскольку метод иногда работает, а иногда нет.

итак, каков хороший способ синхронизации между этими двумя? по сути, я хочу запустить поток прослушивания tcp, получить определенное значение и обновить основную программу.

прилагаются функция приема и функция, которую я использовал для запуска потока. p.s: я совершенно новичок, когда дело доходит до tcp / ip и c #, поэтому любые комментарии к любой части кода или дизайна более чем приветствуются 🙂

 public virtual void Receive()
        {
            string eventMessage = string.Empty;
            int bytesRcvd = 0;
            int totalBytesRcvd = 0;
            byte[] byteBuffer = new byte[maxBufferSize];
            NetworkStream listenStream;
            try
            {
                if (client.Connected)
                {
                    listenStream = client.GetStream();    
                }
                else
                {
                    return;
                }

                while (true)
                {                
                    //message that is slot in from the object will get sent here.
                    if (!string.IsNullOrEmpty(MessageToSend))
                    {
                        Send(MessageToSend);
                        MessageToSend = string.Empty;
                    }

                    // must convert it back and look for the delimiter, cannot wait for the three heartbeat to pass
                    string leftoverMsg = string.Empty;

                    bytesRcvd = listenStream.Read(byteBuffer, totalBytesRcvd, maxBufferSize - totalBytesRcvd);
                    totalBytesRcvd  = bytesRcvd;

                    //if more than heart beat size, can process to see if it's a heartbeat and proceed to send
                    if (totalBytesRcvd > msgHeartbeatSize)
                    {
                        eventMessage = Encoding.ASCII.GetString(byteBuffer, 0, totalBytesRcvd);
                        ProcessMessage(eventMessage, ref leftoverMsg, ref totalBytesRcvd, ref byteBuffer);
                    }
                }
            }
            catch (ThreadAbortException thEx)
            {
                //do nothing as main thread has aborted and waiting to close
                logger.Info(Thread.CurrentThread.Name   " is stopped. ");
            }
            catch (Exception exce)
            {
                bIsActive = false;
                logger.Error(exce);
                CleanUp();
            }
            finally
            {
                logger.Info(String.Format("Thread {0} Exiting. ", Thread.CurrentThread.Name));
            }
        }

public virtual void StartReceivingThread()
        {
            Thread thrReceive = new Thread(Receive);
            try
            {
                if (!bIsActive amp;amp; Connect())
                {
                    //NOTE: exception thrown by a thread can only be captured by that thread itself
                    //start a listen thread
                    //wait until heartbeat message is accepted

                    thrReceive.Name = "thr"   serviceType.Name;
                    thrReceive.Start();
                    bIsActive = true;

                    //wait to get the heartbeat message
                    for (int i = 0; i < maxRetry; i  )
                    {
                        Thread.Sleep(maxTimeOutValue);
                        if (bIsReceivingHeartbeat)
                            break;
                    }
                    //if nothing happens close the connection and try again
                    if (!bIsReceivingHeartbeat)
                    {
                        bIsActive = false;
                        CleanUp();
                        logger.Info("Closing  receiver thread - "   thrReceive.Name);
                    }
                    else
                    {
                        logger.Info("Starting  receiver thread - "   thrReceive.Name);
                    }
                }


            }
            catch(Exception ex)
            {
                logger.Error(ex);
            }
            //finally
            //{
            //    logger.Info("Exiting  receiver thread - "   thrReceive.Name);
            //}
        }
  

Ответ №1:

Я предполагаю bIsReceivingHeartbeat bool , что это переменная-член класса. Если значение, измененное в одном потоке (получателе), не отображается в другом потоке, это, скорее всего, связано с барьером памяти. Я говорю это из моего опыта работы с Java, но это, скорее всего, верно и в .net.

Попробуйте объявить переменные volatile или использовать свойство и синхронизировать получатель и установщик:

 private bool bIsReceivingHeartbeat;
public bool IsReceivingHeartbeat
{
    [MethodImpl(MethodImplOptions.Synchronized)]
    get { return bIsReceivingHeartbeat; }
    [MethodImpl(MethodImplOptions.Synchronized)]
    set { bIsReceivingHeartbeat = value; }
}
  

И в вызывающем коде:

 if (!IsReceivingHeartbeat) ....
  

Я пишу из фона Java, но ситуация, скорее всего, аналогична

Комментарии:

1. да, это так. мм, есть какие-либо предостережения, о которых мне нужно остерегаться при использовании volatile?

2. Существует много вещей, сравнивающих volatile и synchronized. Обычно я предпочитаю последнее, но я не знаю почему. Одно хорошо документированное предостережение относится к такому использованию, как volataileVairable — два потока могут одновременно считывать одно и то же значение и устанавливать для него одно и то же значение, чистый результат, являющийся переменной, увеличивается только один раз, а не дважды. Мне любопытно узнать, исправило ли это вашу проблему или что-то еще является основной причиной.

Ответ №2:

(Похоже, вы также разместили этот код в refactormycode.com .)

В любом случае, вместо цикла с задержкой ожидания я рекомендую использовать объект события, который запускается с помощью кода, который задает IsReceivingHeartbeat . Смотрите классы ManualResetEvent и AutoResetEvent в MSDN.

Комментарии:

1. да, я это сделал, так как хотел получить какой-то ответ на этот дизайн / проблему, но я разместил это там, чтобы люди могли прокомментировать что-нибудь еще.