Потоки. Синхронизация и семафоры. Проблема с реализацией
Я пишу код, который в одном потоке считывает данные из файла и передает их в последовательный порт компьютера. Второй поток возникает по событию DataReceived.
Так вот проблема возникла в связи с тем, что мне необходимо закрывать порт по окончании приема данных, и открывать его снова, при отправке. Но вероятно по причине параллельности выполнения потоков, порт не успевает закрываться. Но если проверяю проверяю с точками останова, ошибки не возникает.
Подскажите пожалуйста каким образом можно избежать данной ошибки.
Насколько я понимаю, необходимо сделать так, что бы первый поток не выполнялся пока не завершится поток по событию DataReceived, но как это сделать, я не знаю.
Проблемный код предоставляю ниже:
{
if (HubsCollection.CheckedItems.Count != 0)
{
thread = new Thread(Do);
thread.IsBackground = false;
thread.Start();
}
else
{
MessageBox.Show("Выберите опрашиваемые концентраторы");
}
}
private void Do()
{
string HubFile = @"c:\counters\data\Hubs.cts";
string con, q, DST_ADDRESS, COUNTER_ADDRESS, COM_PORT_ADDR;
StreamReader StrReader = new StreamReader(HubFile, System.Text.Encoding.Default);
List<string> HubData = new List<string>();
while ((con = StrReader.ReadLine()) != null)
{
HubData.Add(con);
}
for (int y = 0; y < HubData.Count(); y++)
{
q = HubData[y];
string[] Words = q.Split('!');
string hex = "0x";
DST_ADDRESS = Words[2];
COM_PORT_ADDR = Words[3];
COUNTER_ADDRESS = Words[5];
Invoke(new ReceiveHandler(UpdateDisplay), new object[] { "Опрашиваем концентратор: " + DST_ADDRESS + " Счетчик: " + COUNTER_ADDRESS + " Порт: " + COM_PORT_ADDR});
byte[] bytes = new byte[1024];
string DST_ADD_LF = hex + DST_ADDRESS.Substring(0, 2);
string DST_ADD_RT = hex + DST_ADDRESS.Substring(2);
string COUNTER_ADDRESS_LF = hex + COUNTER_ADDRESS.Substring(0, 2);
string COUNTER_ADDRESS_RT = hex + COUNTER_ADDRESS.Substring(2);
byte D_A_L = (Convert.ToByte(DST_ADD_LF, 16));
byte D_A_R = (Convert.ToByte(DST_ADD_RT, 16));
byte C_A_L = (Convert.ToByte(COUNTER_ADDRESS_LF, 16));
byte C_A_R = (Convert.ToByte(COUNTER_ADDRESS_RT, 16));
byte[] data_conf = { 0xff, 0xff, D_A_R, D_A_L, 0x01 };
long CRC = CRC24(data_conf);
byte[] CRC_24_S = BitConverter.GetBytes(CRC);
//Чтение версии прошивки концентратора
byte[] GET_VERINFO = { CRC_24_S[0], CRC_24_S[1], CRC_24_S[2], 0xff, 0xff, D_A_R, D_A_L, 0x01, 0x83, 0x82 };
//чтение текущего времени на коцентраторе
byte[] GET_TIMEDATE = { CRC_24_S[0], CRC_24_S[1], CRC_24_S[2], 0xff, 0xff, D_A_R, D_A_L, 0x01, 0x81, 0x80 };
//чтение текущей конфигурации
byte[] GET_CONFIG = { CRC_24_S[0], CRC_24_S[1], CRC_24_S[2], 0xff, 0xff, D_A_R, D_A_L, 0x01, 0x80, 0x7f };
//чтение информации с почтовых ящиков
byte[] data_rc = { 0xff, 0xff, D_A_R, D_A_L, 0x03 };
long CRC_1 = CRC24(data_rc);
byte[] CRC_24_RC = BitConverter.GetBytes(CRC);
byte[] GET_MAIL = { CRC_24_RC[0], CRC_24_RC[1], CRC_24_RC[2], 0xff, 0xff, D_A_R, D_A_L, 0x03, 0x82, C_A_R, C_A_L, 0x84 };
System.IO.Ports.SerialPort newPort = new SerialPort(COM_PORT_ADDR, 38400, Parity.None, 8, StopBits.One);
newPort.ReadTimeout = 1000;
newPort.RtsEnable = false;
newPort.DtrEnable = true;
if (newPort.IsOpen == true)
{
newPort.Close();
System.Threading.Thread.Sleep(5 * 1000);
}
//Открываем порт и отправляем данные
newPort.Open();
newPort.DataReceived += new SerialDataReceivedEventHandler(newPort_DataReceived);
if (newPort.CtsHolding == true) { } else { };
Send(newPort, GET_VERINFO, GET_TIMEDATE, GET_CONFIG, GET_MAIL);
}
}
private void Send(SerialPort newPort, byte[] GET_VERINFO, byte[] GET_TIMEDATE, byte[] GET_CONFIG, byte[] GET_MAIL)
{
newPort.Write(GET_VERINFO, 0, GET_VERINFO.Length);
newPort.Write(GET_TIMEDATE, 0, GET_TIMEDATE.Length);
newPort.Write(GET_CONFIG, 0, GET_CONFIG.Length);
newPort.Write(GET_MAIL, 0, GET_MAIL.Length);
}
void newPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
SerialPort port = sender as SerialPort;
Thread.Sleep(1000);
int byteRecieved = port.BytesToRead;
byte[] messByte = new byte[byteRecieved];
port.Read(messByte, 0, byteRecieved);
port.Close();
string RecvMsgs = Convert.ToString(messByte.Count());
Invoke(new ReceiveHandler(UpdateDisplay), new object[] {RecvMsgs});
}
PS: Причина, по которой порт необходимо закрывать по окончании приема данных, заключается в том, что в файле, который я читаю в начале порты могут быть разные (образец строки из файла 1!2380!23d8!COM3!1!0003!), а считывать и получать данные от устройств мне необходимо последовательно.
PPS: Я не программист, так что сильно не ругайте за код, просто приходится параллельно выполнять еще и такие задачи. Прислушаюсь к хорошим советам по улучшению кода.
Итак у вас два потока, один читает из файла и пуляет в порт, второй.. ? Вот тут я не вижу четкого описания, вы пишите, что он просыпается по приходу данных иии..? подозреваю, что начинает читать из порта?
Сама задача их синхронизации проста, вам нужен объект синхронизации, причем даже не обязательно объект ядра, как мне кажется. Я не вижу смысла описывать тут все варианты и их особенности, есть замечательная книга Джеффри РИХТЕР "Создание эффективных WIN32-приложений с учетом специфики 64-разрядной версии Windows" . Там есть все по этой теме, разжевано и показано на примерах. Найти ее, даже бесплатно (или главы из нее, вам надо главы 8 и 9) можно в интернете без проблем.
Вам подойдет практически любой: мьютекс, семафор, крит. секция и т.п.
p.s. Если с английским все в порядке, то можно просто посмотреть мсдн, http://msdn.microsoft.com/en-us/library/ms686353%28VS.85%29.aspx .
А и еще , если вам хочется чтобы все было красиво, на классах и т.п. , то можете погуглить в сторону "живых объектов" в c++.
Итак у вас два потока, один читает из файла и пуляет в порт, второй.. ? Вот тут я не вижу четкого описания, вы пишите, что он просыпается по приходу данных иии..? подозреваю, что начинает читать из порта?
Сама задача их синхронизации проста, вам нужен объект синхронизации, причем даже не обязательно объект ядра, как мне кажется. Я не вижу смысла описывать тут все варианты и их особенности, есть замечательная книга Джеффри РИХТЕР "Создание эффективных WIN32-приложений с учетом специфики 64-разрядной версии Windows" . Там есть все по этой теме, разжевано и показано на примерах. Найти ее, даже бесплатно (или главы из нее, вам надо главы 8 и 9) можно в интернете без проблем.
Вам подойдет практически любой: мьютекс, семафор, крит. секция и т.п.
p.s. Если с английским все в порядке, то можно просто посмотреть мсдн, http://msdn.microsoft.com/en-us/library/ms686353%28VS.85%29.aspx .
А и еще , если вам хочется чтобы все было красиво, на классах и т.п. , то можете погуглить в сторону "живых объектов" в c++.
Все бы было отлично, но я забыл уточнить в названии. Платформа .net
Событие newPort.DataReceived += new SerialDataReceivedEventHandler(newPort_DataReceived); Возникает когда в порт поступают данные, а поступить они могут только после выполнения запроса Send(newPort, GET_VERINFO, GET_TIMEDATE, GET_CONFIG, GET_MAIL);
Таким образом я имею точно обозначенную схему обмена информацией и знаю, когда и что должно придти.
Мне посоветовали использовать EnterCriticalSection, LeaveCriticalSection, WaitForSingleObject. Но как использовать данную конструкцию в C# я не знаю
Так вот проблема возникла в связи с тем, что мне необходимо закрывать порт по окончании приема данных, и открывать его снова, при отправке.
Что мешает после того как данные отправлены получать их в том же потоке, а не обрабатывать их в событии?
А как можно событие newPort.DataReceived обработать в том же потоке?
Зачем его обрабатывать, если есть инструменты для синхронного чтения из порта: метод Read нескольких мастей и свойство BaseStream.
Есть метод Read, но как его использовать, если неизвестно получаемое количество байтов и вообще их наличие?
Если с сокетами у нас есть DataAvailable, то тут я нашел на msdn только один аналог DataReceived.
Видите в чем дело. Мне необходимо выполнить определенные процедуры:
1. Открытие порта
2. Отправка данных
3. Получение данных
4. Закрытие порта
Эта процедура должна быть выполнена для каждой строки в файле.
На что я, исходя из задачи выполняю следующее:
1. Читаю данные из файла
2. Формирую пакет на передачу
3. Открываю порт
4. Передаю данные, паралельно открывая событие newPort.DataReceived
5. По событию данные принимаются
Но в этот момент у меня происходит продолжение работы цикла и считывается следующая строка со своими параметрами, а так как в следующей строке тот же порт, то вызывается exception (порт занят).
Мне бы хотелось сделать так, чтобы пока не считаются все данные, цикл не продолжался.
Либо какую-нибудь альтернативу.
Подскажите, как можно использовать BaseStream, а то я нашел только это
PS: Я пытался использовать newPort.ReadExisting, он возвращает System.String в соотвествии с кодировкой. Мне бы просто массив байтов получить
Как это сделать?