Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: VS2010 прием по TCP
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > Программирование
smk
Цитата
Недопустимая операция в нескольких потоках: попытка доступа к элементу управления 'chart1' не из того потока, в котором он был создан.

В приложении, которое мне необходимо для работы нужно принимать пакет данных по ТСП и "распихивать" их по элементам главной формы. Прием ведется в отдельном потоке:
Код
                Thread th = new Thread(ReceiveRun);
                th.Start();


        // Цикл извлечения сообщений,
        // запускается в отдельном потоке.
        void ReceiveRun()
        {
            while (true)
            {
                try
                {
                    string s = null;
                    while (ns.DataAvailable == true)
                    {
                        //// Определение необходимого размера буфера приема.
                        //byte[] buffer = new byte[_tcpСlient.Available];

                        ns.Read(inBuffer, 0, inBuffer.Length);
                        s += Encoding.Default.GetString(inBuffer);
                    }

                    if (s != null)
                    {
                        ShowReceiveMessage(s);
                        s = String.Empty;
                    }


                    // Вынужденная строчка для экономия ресурсов процессора.
                    // Неизящный способ.
                    Thread.Sleep(100);
                }
                catch
                {
                    ErrorSound();
                }

                if (_stopNetwork == true) break;

            }
        }



При попытке получить пакет появляется ошибка, которую я привел в начале этого сообщения. Ошибка появляется при обработке функции отображения данных:

Код
       // Код доступа к свойствам объектов главной формы  из других потоков
        delegate void UpdateReceiveDisplayDelegate(string message);
        void ShowReceiveMessage(string message)
        {
            if (chart1.InvokeRequired == true)
            {
                UpdateReceiveDisplayDelegate rdd = new UpdateReceiveDisplayDelegate(ShowReceiveMessage);

                // Данный метод вызывается в дочернем потоке,
                // ищет основной поток и выполняет делегат указанный в качестве параметра
                // в главном потоке, безопасно обновляя интерфейс формы.
                Invoke(rdd, new object[] { message });
                chart1.Series[0].Points.Clear();
                chart1.Series[1].Points.Clear();
                chart1.Series[2].Points.Clear();
                chart1.Series[3].Points.Clear();
                chart1.Series["1,3 мкм"].Points.AddY(inBuffer[1] << 8 + inBuffer[0]);
                chart1.Series["2,11 мкм"].Points.AddY(inBuffer[3] << 8 + inBuffer[2]);
                chart1.Series["1,8 мкм"].Points.AddY(inBuffer[5] << 8 + inBuffer[4]);
                chart1.Series["1,93 мкм"].Points.AddY(inBuffer[7] << 8 + inBuffer[6]);
            }
            else
            {
                // Если не требуется вызывать метод Invoke, обратимся напрямую к элементу формы.
                chart1.Series[0].Points.Clear();
                chart1.Series[1].Points.Clear();
                chart1.Series[2].Points.Clear();
                chart1.Series[3].Points.Clear();
                chart1.Series["1,3 мкм"].Points.AddY(inBuffer[1] << 8 + inBuffer[0]);
                chart1.Series["2,11 мкм"].Points.AddY(inBuffer[3] << 8 + inBuffer[2]);
                chart1.Series["1,8 мкм"].Points.AddY(inBuffer[5] << 8 + inBuffer[4]);
                chart1.Series["1,93 мкм"].Points.AddY(inBuffer[7] << 8 + inBuffer[6]);
            }


        }


Как правильно поступить с выводом информации? Прошу помочь. Спасибо.
XVR
У вас в ShowReceiveMessage жуткая мешанина из того, что нужно вызывать в потоке приема и того, что нужно вызывать в GUI потоке.

В потоке приема нужно вызывать ТОЛЬКО Invoke от делегата (от ShowReceiveMessage), а в самой ShowReceiveMessage уже обновлять всю GUI часть
smk
Цитата(XVR @ Nov 25 2015, 08:37) *
У вас в ShowReceiveMessage жуткая мешанина из того, что нужно вызывать в потоке приема и того, что нужно вызывать в GUI потоке.

В потоке приема нужно вызывать ТОЛЬКО Invoke от делегата (от ShowReceiveMessage), а в самой ShowReceiveMessage уже обновлять всю GUI часть

Спасибо, что откликнулись. Можно попросить уточнить (на пальцах) что куда перенести? Прошу простить, не силен в этой стороне программирования и прибегаю только когда действительно не обойтись. В идеале если есть у Вас какая-то "коза" с правильной организацией вопроса, то был бы очень признателен. А вообще хотелось бы так чтобы в одном потоке происходил прием, а вдругом обработка. Пришло что-то - выставили флаг. В обработчике увидели флаг - обработали что пришло. Как правильно сделать тсп-клиент под это я не знаю. Потому нужна помощь. С остальным, думаю разберусь. Спасибо.
XVR
Конструкцию UpdateReceiveDisplayDelegate rdd = new UpdateReceiveDisplayDelegate(ShowReceiveMessage); вынести из ShowReceiveMessage и поместить в начало ReceiveRun
Конструкцию Invoke(rdd, new object[] { message }); вынести из ShowReceiveMessage и пометить в ReceiveRun вместо вызова ShowReceiveMessage
Прямые обращения к inBuffer в ShowReceiveMessage заменить на обращения к ее декодированному содержимому, декодировать его из параметра message
В ShowReceiveMessage ветку под if (chart1.InvokeRequired == true) убрать
smk
Благодарю. Сделаю как Вы сказали. Еще раз спасибо.
smk
Цитата(XVR @ Nov 25 2015, 12:15) *
Конструкцию Invoke(rdd, new object[] { message }); вынести из ShowReceiveMessage и пометить в ReceiveRun вместо вызова ShowReceiveMessage

Сделал. Пишет, что "Элемент "message" не существует в текущем контексте." Можно с этим что-то сделать? Спасибо.
XVR
В контексте ReceiveRun этот элемент называется s. Т.е. надо Invoke(rdd, new object[] { s });
smk
Пока получается. Остается вопрос по кнопке "отсоединиться". Отсоединиться получается, но потом не соединяется т.к. элемент _tcpСlient ликвидирован. Что можно сделать? И еще. Я тут "козу" набросал. Посмотрите пожалуйста на предмет правильности и нужно как-то сделать так чтоб void Display() выводила в отдельном потоке. По событию таймера выводит. Заранее прошу прощения за навязчивость, но помощи ждать больше неоткуда. Если удастся привести проект в порядок, то будет мне коза на все случаи жизни.
Нажмите для просмотра прикрепленного файла
smk
И еще такой момент. тспклиент принимает ровно 16 посылок. не понимаю что его ограничивает.
Собственно проект, который принмает только 16 посылок. Нажмите для просмотра прикрепленного файла
Даже не представляю как так получается. Ничего такого и близко нет. Может кто поможет понять что за чудеса? Спасибо.
XVR
Цитата(smk @ Nov 28 2015, 12:42) *
Пока получается. Остается вопрос по кнопке "отсоединиться". Отсоединиться получается, но потом не соединяется т.к. элемент _tcpСlient ликвидирован. Что можно сделать?
Я не в курсе, можно ли вызвать Connect у TCPClient после Close, но если нельзя, то можно заново создать TCPClient:
Код
        private void button2_Click(object sender, EventArgs e)
        {
            tcpСlient.Close();
            tcpStream.Close();
            tcpСlient = new TcpClient();
        }

Цитата
нужно как-то сделать так чтоб void Display() выводила в отдельном потоке.
Для этого нужен отдельный поток и вызывать оттуда сам Display через делегат
Вызывать экранные элементы из GUI можно ТОЛЬКО из того потока, где они создавались
Цитата
И еще такой момент. тспклиент принимает ровно 16 посылок. не понимаю что его ограничивает.

Может сервер закрывает соединение?
smk
Цитата(XVR @ Nov 30 2015, 13:47) *
Может сервер закрывает соединение?

Точно нет. проверил. Есть такая программка - hercules-3-2-8. С ней работает на ура. Вот бы посмотреть как сделано.
XVR
А что происходит после приема 16 посылок? Останавливается поток или закрывается соединение? Или еще что то?
Цитата
Есть такая программка - hercules-3-2-8. С ней работает на ура.
Может надо что то серверу передавать? Посмотрите WireShark'ом обмен с hercules-3-2-8
smk
Цитата(XVR @ Dec 1 2015, 13:42) *
А что происходит после приема 16 посылок? Останавливается поток или закрывается соединение? Или еще что то?
Может надо что то серверу передавать? Посмотрите WireShark'ом обмен с hercules-3-2-8

После 16 посылок перестают отображаться новые данные, хотя они передаются. Шарком смотрел. Связь организована именно путем запрос-ответ. Геркулес нормально общается, передает запрос и получает ответ сколько угодно раз. Еще раз сегодня все уточню что в сети происходит для порядка...
Сервер это контроллер с кейловским стеком. Он и реньше нормально работал и сейчас повода для претензий не вижу.
sergey sva
Может что то с отображением данных в gui.
Попробуйте вставить лог в это место
Код
  if (s != null)
{
запись в лог s
ShowReceiveMessage(s);
        s = String.Empty;
}
smk
В прикрепленном проекте все хорошо кроме приема данных. Соединяется, передает, разъединяется, повторно соединяется. Принимает сколько угодно пакетов. Но при приеме никак не выходит отобразить данные, хотя доподлинно известно, что они есть и валидны. Как только пытаюсь что-то вывести примет первый пакет и все. Счетчик показывает 1 и не меняется. Если часть вывода закоментировать, то выводит нули, хотя знаю что не должно быть нулей. Может как-то неправильно в функцию вывода передается массив, но при этом счетчик выводит правильно. Помогите сделать прием пожалуйста. Я уже не знаю что можно еще предпринять.
Нажмите для просмотра прикрепленного файла
Как-то трудно на словах описать внятно. Если кто сможет, просто попробуйте.
XVR
Чтение у вас сделано мягко говоря необычно sm.gif

Ваш код с комментариями

Код
                            byte[] bytes = new byte[tcpСlient.ReceiveBufferSize];
// Заказываете буфер для чтения неизвестно какого размера -
//  то, что вернет tcpСlient.ReceiveBufferSize тут и то, что он вернет 3мя строками позже не обязательно совпадают

                            while (tcpStream.DataAvailable == true)
// Если данные есть - читаем их, если нет - то не читаем, а выдаем пустой buffer
// (returndata во 2й раз будет не null, а String.Empty)
                            {
                                tcpStream.Read(bytes, 0, (int)tcpСlient.ReceiveBufferSize);
                                returndata = Encoding.UTF8.GetString(bytes);
// Это зачем ???
                                count++;
                            }
                            if (returndata != null)
// Это просто жесть :)
                            {
                                Invoke(rdd, new object[] { bytes });
                                str = returndata;
                                returndata = String.Empty;
                            }

Код жуткий, кроме того, он выдаст нечто нарезанное далеко не по границам пакетов
Пакет нужно собирать в приемной нити, и отдавать целиком в Display
smk
Спасибо. Что-то я догадываюсь как исправить, но "жесть" даже не понимаю куда двигаться. Приложение нужно уже вчера. Может поможете?

А как сделать так чтоб обычно? Главное надежность. Посылка приходит каждые 30-40 мС.
XVR
returndata и все манипуляции вокруг него не нужны вообще
Вызывать Invoke(rdd,...) нужно сразу после tcpStream.Read и передавать в него разиер реально прочтенных данных, но лучше все же собирать пакет после tcpStream.Read, и только готовый отдавать на показ

Какой формат принимаемых пакетов?

smk
Цитата(XVR @ Dec 2 2015, 22:38) *
returndata и все манипуляции вокруг него не нужны вообще
Вызывать Invoke(rdd,...) нужно сразу после tcpStream.Read и передавать в него разиер реально прочтенных данных, но лучше все же собирать пакет после tcpStream.Read, и только готовый отдавать на показ

Какой формат принимаемых пакетов?

Спасибо. Формат сейчас 256 байт вне зависимости от того сколько реально значащих. К-во значащих зависит от содержания запроса, остальные при формировании буфера передачи просто не модифицируются. В целом размер передаваемого буфера всегда будет не более 256 байт и может меняться только в сторону уменьшения. Оптимольный размер покажет практика. Хочу спросить, что значит "собирать пакет"? Как это делается?

Вот так сейчас сделал. Счетчик тикает, данные идут, отображения всеравно нет.


Код
        private void ReceiveRun()
        {
            
            UpdateReceiveDisplayDelegate rdd = new UpdateReceiveDisplayDelegate(Display);
            try
            {
                while (true)
                {
                        if (tcpStream.CanRead)
                        {
                            byte[] bytes = new byte[256];
                            while (tcpStream.DataAvailable == true)
                            {
                                tcpStream.Read(bytes, 0, (int)256);
                                count++;
                            }
                            Invoke(rdd, new object[] { bytes });
                         }
                        else
                        {
                            label1.Text = "Прием невозможен";
                            th.Abort();
                            tcpСlient.Close();
                            tcpStream.Close();
                            return;
                        }
                 }
            }
            catch
            {
                label1.Text = "ошибка";
            }
        }

        delegate void UpdateReceiveDisplayDelegate(Byte [] mydata);
        private void Display(Byte[] mydata) //вывод на форму
        {
            Int32 temp;
            temp = ((mydata[3] << 24) + (mydata[2] << 16) + (mydata[1] << 8) + mydata[0]);
            label1.Text = temp.ToString("D");
            label3.Text = Convert.ToString(count);
                //if (mydata[0] > 0) label2.Text = "OK";
                //else label2.Text = "0";
        }
XVR
Цитата
Хочу спросить, что значит "собирать пакет"?
Это значит, что TCP соединение не является datagram ориентированным. Т.е. это просто поток байтов. И никто не гарантирует, что приемная сторона будет получать эти байты кусками такого же размера, как передавала передающая сторона. Т.е. вы свои 256 байт можете получить в несколько чтений (и мелкой нарезкой).

Цитата
Как это делается?
Принимаются данные и накапливаются в буфере, пока не наберется полный пакет.

Цитата
while (tcpStream.DataAvailable == true)
{
tcpStream.Read(bytes, 0, (int)256);
count++;
}
Invoke(rdd, new object[] { bytes });
Это неправильно. У вас в буфере будет мешанина из кусков принятых данных. Надо как то так
Код
byte[] bytes = new byte[256];
...

int size = 0;
while(size<256)
{
  while (tcpStream.DataAvailable == true)
   {
     size+=tcpStream.Read(bytes, size, 256-size);
   }
  if (size<256) Thread::Sleep(100);
}
count++;

Invoke(rdd, new object[] { bytes });

smk
Судя по режиму отладки функция Display выполняется очень часто. И действительно в буфере нули. Если и приходит что-то, то оно сразуже затирается нулями при следующем вызове Display. Как красиво сделать так чтоб Display вызывалась только если приняты свежие данные?
Пока сделал вот так:
Код
if(count_old != count)Invoke(rdd, new object[] { bytes });

Но всеравно в Display передаются нули.
XVR
Нужно правильно принимать. См сообщение №20
smk
Кажись заработало. пока-что... Тестирую. Спасибо! Поглядывайте пока в тему пожалуйста... пока тестирую.
smk
Вот что получается. Если я кнопкой отправляю 16 запросов то после 16-го все последующие не обрабатываются. Ну или еще один с большой задержкой. Если я командую прибору отвечать без запроса - молотит без сбоев. Если геркулесом отправляю запросы - тоже никаких проблем, отвечает хоть на 50-й. Даже не представляю каким способом вычислить этот затык. Можно что-то придумать? Спасибо.

Содержание пакета не имеет значения. Просто перестает отправлять или вообще подвисает. На мой непрофессиональный взгляд все вполне должно работать.
Код
       private void button3_Click(object sender, EventArgs e)//Отправить
        {

            if (tcpStream.CanWrite)
            {
                outBuffer[0] = 1;
                if (checkBox1.Checked)
                {
                    outBuffer[1] = 2;
                    outBuffer[2] = 1;
                }
                else
                {
                    outBuffer[1] = 2;
                    outBuffer[2] = 0;
                }
  
                tcpStream.Write(outBuffer, 0, outBuffer.Length);
            }
            else
            {
                label1.Text = "Передача невозможна";
                th.Abort();
                tcpСlient.Close();
                tcpStream.Close();
                return;
            }
        }


Как я успел понять - программа ничего не отсылает. Почему - непонятно.
smk
похоже, что проблема со стороны прибора. проверю - напишу.
XVR
А чему равен outBuffer.Length ?
smk
Цитата(XVR @ Dec 4 2015, 12:15) *
А чему равен outBuffer.Length ?

всегда 256. проверял. Насколько я успел понять, то с приемной стороны забивается окно приема. Выправил, но не проверил еще.
smk
Все отлично работает. Прием и передача ОК. Огромное спасибо!
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.