21 / 21 / 0
Регистрация: 30.08.2012
Сообщений: 122
Записей в блоге: 5
1

Подвисает при чтении COM порта

22.11.2013, 03:00. Показов 12221. Ответов 23
Метки нет (Все метки)

Author24 — интернет-сервис помощи студентам
Доброго времени суток, сделал программу, которая читает данные из COM порта, строка кода которая считывает данные стоит в компоненте timer с периодичностью n, поставил т.к. threading вызывает зависание программы, это неприемлемо. Но в таймере почему-то эта строка тоже зависает, данные то отображаются с COM порта на форме и обновляются но работать с формой невозможно. Что можно придумать? Возможно проверка
if (данные на ком порте не пустые) погнали...
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        SerialPort port = new SerialPort( "COM3" , 9600, Parity.None, 8, StopBits.One);
        int i = 0;
        string str, str2;
        private void Form1_Load(object sender, EventArgs e)
        {
            port.Open();
            timer1.Enabled = true;
        }
 
        private void timer1_Tick(object sender, EventArgs e)
        {
            str = port.ReadLine();
            str2 = port.ReadLine();
            label1.Text = str;
            label2.Text = str2;
        }
0
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
22.11.2013, 03:00
Ответы с готовыми решениями:

Зависает форма при чтении из порта
Пишу программку, которая читает данные из серийного порта. Но форма повисает, пока чтение не...

Чтение из COM порта, При чтении из порта зависает read()
Каждому рано или поздно приходится программировать com порт. Вот и мой черед пришол. Я ужу умею:...

«Зависает» при чтении com порта
Здравствуйте! Алгоритм программы следующий: «Читать до конца файла» 1.1 Программный reset...

Зависает при чтении com порта
Привет всем, подскажите, пожалуйста, у меня есть прибор, с которого я считываю данные каждую...

23
27 / 27 / 1
Регистрация: 19.09.2012
Сообщений: 123
22.11.2013, 03:15 2
Просто читать из порта надо в отдельном рабочем потоке, посмотрите в сторону async/await. Инициировать чтение можете продолжать по таймеру, но само чтение лучше реализовать в отдельной async-функции, после await'а которой записывать результат в label'ы. К сожалению, не могу сейчас написать код, но наверное разберётесь...
1
21 / 21 / 0
Регистрация: 30.08.2012
Сообщений: 122
Записей в блоге: 5
22.11.2013, 15:02  [ТС] 3
Спасибо, буду искать, если там вдруг пример найдете скиньте пож-ста.
0
21 / 21 / 0
Регистрация: 30.08.2012
Сообщений: 122
Записей в блоге: 5
23.11.2013, 16:50  [ТС] 4
Все разобрался!
Вот код программы:
C#
1
2
using System.IO.Ports;
using System.Threading;
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public partial class Form1 : Form
    {
        BackgroundWorker worker;
        public Form1()
        {
            InitializeComponent();
            worker = new BackgroundWorker();
            worker.WorkerReportsProgress = true;
            worker.DoWork += SetBrightness;
        }
        SerialPort port = new SerialPort( "COM3" , 9600, Parity.None, 8, StopBits.One);
        int i = 0;
        static string str, str2;
 
        public void SetBrightness(object sender, DoWorkEventArgs e)
        {
            for (; ; )
            {
                str = port.ReadLine();
                str2 = port.ReadLine();
            }
        }
 
        private void Form1_Load(object sender, EventArgs e)
        {
            port.Open();
            timer1.Enabled = true;
        }
 
 
        private void timer1_Tick(object sender, EventArgs e)
        {
            label1.Text = str;
            label2.Text = str2;
        }
 
        private void button1_Click(object sender, EventArgs e)
        {
                worker.RunWorkerAsync(100);
        }
Все работает как надо, если есть рекомендации по оптимизации пишите! Заранее спасибо!
0
48 / 48 / 22
Регистрация: 18.11.2013
Сообщений: 92
23.11.2013, 22:18 5
кхм, а то что в обработчик дуворк зациклен это так задумано?)
и зачем вы передаете 100 в RunWorkerAsync? вы же их нигде не используете
а вообще тут нужно знать как и что приходит в последовательный порт (что на том конце провода)
если подключенный девайс шлет одну за другой строчки с интервалом в 15 секунд как я понял...я бы сделал как то так
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
        //это понадобится ниже
        static readonly char[] separator = { '\r', '\n' }; 
 
        //событие происходящее когда в приемном буфере COM порта появляются байты 
        private void port_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {
            worker.RunWorkerAsync(1000);
        }
 
        //асинхронный программе поток воркера
        private void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            //вытаскиваем аргумент из worker.RunWorkerAsync(1000);
            Int32 arg = (Int32)e.Argument; //теперь это наша 1000
            
            /*воркер запщуен из события датаресивед, чтобы гарантированно получить все байты
              ежили строки посылаются одна за другой (это как я понял), ждем 1секунду.
              Опять же это выполняется асинхронно основной программе поэтому зависания в форме не будет*/
            Thread.Sleep(arg);
            //читаем все принятые байты
            String str = port.ReadExisting();
            
            //это элементарная проверка на то что собственно прилетело в ком порт
            String[] strArr = str.Split(separator, StringSplitOptions.RemoveEmptyEntries);
 
            /*разбиваем полученную строку по признаку конца строки \r\n и если в полученном
              массиве 2 строки, то мы хотя бы знаем что на входе было именно 2 строки а не какой то шум
              или сбой девайса, а вообще по хорошему нужно какое то протоколирование) */
 
            if (strArr.Count().Equals(2))
                e.Result = strArr;
            else //в противном посылаем сигнал об отмене события
                e.Cancel = true; 
        }
 
        //сюда мы попадаем когда закочился DoWork
        private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            //если событие было отменено выходим
            if (e.Cancelled)
                return;
            //приводим аргумент события к нужной нам форме
            String[] strArr = (String[])e.Result;
            //и отсюда уже заполняем текст
            label1.Text = strArr[0];
            label2.Text = strArr[1];
        }
        
        //таким образом таймер нафиг не нужен
за 100% работоспособность кода не отвечаю, набросал на ходу
1
21 / 21 / 0
Регистрация: 30.08.2012
Сообщений: 122
Записей в блоге: 5
23.11.2013, 22:34  [ТС] 6
На другой стороне arduino mega, шлет значения пока с датчика расстояния или просто текст, но планирую с датчиков температуры и влажности. Вот код на arduino:
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void setup()
{
  Serial.begin(9600);
   }
 
void loop()
{
  Serial.println("23.9C");
  delay(100);
  Serial.println("146.9F");
  delay(1000);
//////////////////имитация изменения значений
  Serial.println("15.6C");
  delay(100);
  Serial.println("138.6F");
  delay(1000);
 
}
Не было времени долго углубляться изучение потоков. Но похоже надо мой код подредактировать.
Что означает 1000 в worker.RunWorkerAsync(1000);

В в этом градуснике, я собираюсь посылать значения с большим (1000++) интервалом, а что если arduino высылает их каждые 50мс, 25мс, как их лучше принимать в C#?
Спасибо!
0
48 / 48 / 22
Регистрация: 18.11.2013
Сообщений: 92
23.11.2013, 23:05 7
Цитата Сообщение от vladislav-glav Посмотреть сообщение
Что означает 1000 в worker.RunWorkerAsync(1000);
1000 это просто передаваемый параметр обработчику DoWork
его можно вытащить из аргумента передаваемого в событие DoWork - DoWorkEventArgs e что собственно и сделано в коде, далее это значение используется чтобы усыпить поток для гарантированного получения 2х строк, но как оказалось они шлются с интервалом в ~1100мс, поэтому можете изменить его на 300-500

Цитата Сообщение от vladislav-glav Посмотреть сообщение
В в этом градуснике, я собираюсь посылать значения с большим (1000++) интервалом, а что если arduino высылает их каждые 50мс, 25мс, как их лучше принимать в C#?
если это градусник, вам не нужны такие интервалы, температура вещь инертная, но если все же если понадобится, то конечно же лучше принимать в событии DataReceived ком порта, кидать полученные данные в какой то промежуточный буфер, при этом сделать какой нибудь элементарный протокол (чтобы отличать эти 2 сообщения друг от друга) и парсить (анализировать) принятые данные (это будет в DoWork скорее всего) а потом уже что то делать с данными(это в RunWorkerCompleted)

Добавлено через 13 минут
а стоп, а на кой черт посылать значения температуры в цельсиях и фаренгейтах?
посылайте только одно и конвертируйте на приемной стороне, только сейчас понял, что там у вас было
1
27 / 27 / 1
Регистрация: 19.09.2012
Сообщений: 123
23.11.2013, 23:21 8
Вот так это будет выглядеть с async/await, и по сути ничего не надо больше:

C#
1
2
3
4
5
6
7
8
9
10
11
12
private async void timer1_Tick(object sender, EventArgs e)
{
    timer1.Stop();
    var strings = await Task.Run(() => new[]
    {
        port.ReadLine(),
        port.ReadLine()
    });
    label1.Text = strings[0];
    label2.Text = strings[1];
    timer1.Start();
}
1
48 / 48 / 22
Регистрация: 18.11.2013
Сообщений: 92
23.11.2013, 23:36 9
Ithilgwau, у вас "небезопасный" прием, мы просто считываем 2 строчки как и в первоначальном варианте
конечно если цель оправдывает средства...но я бы так не сделал
у ТСа вообще девайс шлет эти 2 строчки с ~1100мс интервалом, а таймер взведен на 15 секунд, считываются не все данные...буфер ком порта по дефолту имеет вместимость в 4096 (вроде) байта, при таком подходе он просто переполнится и начнет перезаписываться (или вбросит исключение, не знаю точно), что приведет к потере данных
1
27 / 27 / 1
Регистрация: 19.09.2012
Сообщений: 123
24.11.2013, 02:15 10
Цитата Сообщение от nb-np Посмотреть сообщение
у вас "небезопасный" прием, мы просто считываем 2 строчки как и в первоначальном варианте
Вопрос был о том, как сделать, чтобы UI не зависал. Я на это и ответил. А насчёт буфера ком-порта и т.д. - это уже другой вопрос Думаю, тут надо беспрерывное чтение конечно сделать... И если не прибегать к событиям, это можно сделать через тот же таймер, только он будет читать не из ком-порта, а из собственного буера приложения...
1
21 / 21 / 0
Регистрация: 30.08.2012
Сообщений: 122
Записей в блоге: 5
24.11.2013, 05:39  [ТС] 11
Спасибо! Я думаю это даст мне предпосылки двигаться дальше в работе с потоками. Раньше думал, что потоки это очень сложно...
А по поводу пакетов, да уже я придумал нечто.
0
27 / 27 / 1
Регистрация: 19.09.2012
Сообщений: 123
24.11.2013, 10:38 12
Цитата Сообщение от vladislav-glav Посмотреть сообщение
Раньше думал, что потоки это очень сложно...
Так-то потоки это не очень просто конечно, особенно если только начинаешь в них разбираться... Но создатели C# - молодцы. Придумав async/await, они очень упростили реализацию разработчиками значительной части задач...

Но вот если делать так, чтобы поток читал COM-порт беспрерывно, то async/await тут уже не обойтись... Тут придётся создавать поток (Thread.Start) при запуске формы, в этом потоке в бесконечном цикле читать порт и через диспетчер устанавливать label'ы на форме (через диспетчер - потому что напрямую это делать не из главного потока нельзя, у нас это разруливал await, а сейчас некому). Последнее делается как-то так:
C#
1
2
3
4
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(()=>{
        label1.Text = str;
        label2.Text = str2;
    }));
Кроме этого, рабочий поток надо как-то завершать (иначе приложение просто останется в памяти навсегда). Можно конечно сделать thread.Abort при закрытии окна, но это не очень хороший способ. Поэтому лучше сделать цикл чтения порта не бесконечным, а с проверкой на булевскую переменную: while (!bFinishThread) {...} И при закрытии окна просто устанавливать bFinishThread в true.

Добавлено через 11 минут
Ой, у тебя же Forms, а не WPF... Вот тут хорошо про диспетчер объясняется (и для Forms, и для WPF) http://usanov.net/792-multipot... orms-i-wpf
0
48 / 48 / 22
Регистрация: 18.11.2013
Сообщений: 92
24.11.2013, 23:53 13
ради интереса расчехлил эклипс и какую то AVRку и проверил с железом
вариант с воркером и DataReceived "небезобасный" с точки зрения многопоточности, событие DataReceived происходит не в основном потоке, и походу если воркер запущен в этом потоке событие RunWorkerCompleted происходит так же в нем, в студии при отладке вбрасывается InvalidOperationException, посему возможно Invoke остается единственным "правильным" решением
0
27 / 27 / 1
Регистрация: 19.09.2012
Сообщений: 123
25.11.2013, 00:09 14
Я уже достаточно работал с потоками с C#, но про этот worker слышу впервые Что это вообще, и зачем он нужен? Есть ведь объект Thread, есть Task, есть диспетчеризация, есть различные объекты синхронизации... Да ещё и async/await в 4.5...
0
48 / 48 / 22
Регистрация: 18.11.2013
Сообщений: 92
25.11.2013, 02:09 15
в общем виде как то так
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
        private void port_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {
            /* момент генерации события точно не известен, поэтому чтобы принять все символы
             * ждем немного (правда я отправлял без задержки обе строки) */
             
            Thread.Sleep(300);
            String str = port.ReadExisting();
            String[] strArr = str.Split(separator, StringSplitOptions.RemoveEmptyEntries);
            //тут может быть проверка на предмет сообщения, но я ее убрал
            LabelsRefreshSafe(strArr);
        }
 
        //потоково безопасный метод
        private void LabelsRefreshSafe(String[] strArr)
        {
            if(this.InvokeRequired)
                this.Invoke(new Action<String[]>((d) => LabelsRefresh(d)), new Object[] { strArr });
            else
                LabelsRefresh(strArr);
        }
 
        //обычный метод обновления лейблов
        private void LabelsRefresh(String[] str)
        {
            this.label1.Text = str[0];
            this.label2.Text = str[1];
        }
0
27 / 27 / 1
Регистрация: 19.09.2012
Сообщений: 123
25.11.2013, 02:42 16
Цитата Сообщение от nb-np Посмотреть сообщение
Thread.Sleep(300);
Что-то Вы, сударь, нагородили... Код, в котором есть Sleep - это всегда ненормальный код, это всегда хак. Просто никогда и нигде не используйте Sleep, только в целях отладки (чтобы, например, сэмулировать какую-то продолжительную операцию). Здесь нужно использовать или ReadLine или Read. И не понадобится никаких port_DataReceived. Всё это делается проще и понятнее, как я писал выше:
Цитата Сообщение от Ithilgwau Посмотреть сообщение
Тут придётся создавать поток (Thread.Start) при запуске формы, в этом потоке в бесконечном цикле читать порт и через диспетчер устанавливать label'ы на форме
Ииииии всё
0
48 / 48 / 22
Регистрация: 18.11.2013
Сообщений: 92
25.11.2013, 14:35 17
Цитата Сообщение от Ithilgwau Посмотреть сообщение
это всегда хак
но
Цитата Сообщение от Ithilgwau Посмотреть сообщение
в бесконечном цикле читать порт
это тоже хак)

вы знаете что происходит при попытке прочитать несуществующий порт? (девайс отвалился)
приложение намертво зависает
а вы знаете что может быть в первоначально рассматриваемом случае ТСа?
девайс и приложение работают асинхронно относительно друг от друга, нет никакой гарантии что чтение не начнется в момент прихода нового сообщения (скорость обмена значительно ниже скорости считывания)

по хорошему тут приемлем только событийный режим, да, мой вариант "урощен", да, там есть слип в асинхронном потоке (никому не мешающий), да, по хорошему там должен быть какой нибудь FIFO буфер и использование BytesToRead и прочего, а обмен данным должен быть осуществлен по какому нибудь протоколу (хотя бы самопальному)...но это уже другая история
0
27 / 27 / 1
Регистрация: 19.09.2012
Сообщений: 123
25.11.2013, 14:54 18
Цитата Сообщение от nb-np Посмотреть сообщение
это тоже хак)
Нет, это не хак, это обычная нормальная практика. И, кстати, я уточнил, что цикл не должен быть бесконечным по-хорошему, а надо проверять на булевскую переменную, чтобы корректно выйти из него.
Цитата Сообщение от nb-np Посмотреть сообщение
девайс и приложение работают асинхронно относительно друг от друга, нет никакой гарантии что чтение не начнется в момент прихода нового сообщения
Да и Бог с ним, пусть начнётся. У нас отдельный поток, и пусть читает как ему угодно.
Цитата Сообщение от nb-np Посмотреть сообщение
слип в асинхронном потоке (никому не мешающий)
В любом потоке Sleep - зло. Я просто не понимаю, почему нельзя сделать просто Read или ReadLine, которые блокируют поток до завершения чтения?
0
48 / 48 / 22
Регистрация: 18.11.2013
Сообщений: 92
25.11.2013, 15:31 19
Ithilgwau, не, Вы не поняли
во-первых я сделал акцент на событийном режиме, я не оправдываю слип, во-вторых зачем городить еще один поток если мы имеет DataReceived?
Цитата Сообщение от Ithilgwau Посмотреть сообщение
Read или ReadLine
я же говорю, если девайс отвалится, порт будет "типа открытый" (IsOpen == true) но поток выполняющий ReadLine застопоривается (я там выше описался, написал "приложение" вместо "поток"), а в случае DataReceived просто не будет вызываться...

Не по теме:

широко известный Modbus работающий по таймаутам вы тоже предложите в бесконечном цикле опрашивать ReadLine'ом ? ;)

0
27 / 27 / 1
Регистрация: 19.09.2012
Сообщений: 123
25.11.2013, 15:45 20
Цитата Сообщение от nb-np Посмотреть сообщение
во-вторых зачем городить еще один поток если мы имеет DataReceived
Так дело в том, что DataReceived просто не нужен. Он только усложняет всё. Пусть среда сама разруливает, как и что она читает, и возвращает нам управление, когда уже всё прочитано, тогда ни о каких задержках и кол-ве данных в буфере беспокоиться уже не нужно
Цитата Сообщение от nb-np Посмотреть сообщение
поток выполняющий ReadLine застопоривается
Он не застопориться, если поставить timeout чтения (который и так наверное по умолчанию задан). И пусть порт будет открыт на здоровье, как только по нему придут данные, Read их вернёт.
0
25.11.2013, 15:45
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
25.11.2013, 15:45
Помогаю со студенческими работами здесь

При чтении com порта зависает приложение
Вот эта строка кода вешает всё приложение: textBox1-&gt;Text = serialPort1-&gt;ReadLine(); Если её...

Зацикливание при чтении COM-порта в функции ReadFile()
Создал класс Transmit для работы с виртуальным COM-портом ПК. Transmit.h #include...

Откуда при чтении из COM-порта берутся 99 байт?
Здравствуйте! Этот отрывок кода взят из рабоче программы, которая работает около 2-х лет и судя по...

Работа функции FlushFileBuffers при синхронном чтении из Com-порта
Всем здравствуйте. Пытаюсь освоить работу с Com-портом средствами API. В описании к функции...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
20
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru