Пользовательский интерфейс Windows зависает и не может выполнить действие пользователя, когда поступает слишком много данных - PullRequest
0 голосов
/ 29 мая 2019

Создана простая оконная форма для приема и обработки данных из последовательного порта также регистрация данных в текстовом файле и отображение в поле расширенного текста.

По какой-то причине он переходит в состояние отсутствия ответа и не может выполнять действия пользователя и зависать.

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

Но теперь, когда я использую фоновый рабочий, он должен создать другой поток для обработки и добавления в поле log и richtext. Это правильно?

Это мой первый проект на c #, так что извините, если на него уже ответили где-то еще, так как я не могу соотнести эту ситуацию с данными ответами и не могу их реализовать.

Это хорошая идея для фонового работника, если нет, то как я могу решить эту проблему.

Заранее спасибо.

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

//earlier code
void DataReceived_Event(object sender, SerialDataReceivedEventArgs e)
{
    if (Port.IsOpen)
    {
        int bytes = Port.BytesToRead;
        byte[] buffer = new byte[bytes];
        Port.Read(buffer, 0, bytes);
        receivedBytes.AddRange(buffer);
        ProcessRecievedBytes(null);
    }
}

//latest code with background worker
void DataReceived_Event(object sender, SerialDataReceivedEventArgs e)
{
    if (Port.IsOpen)
    {
        int bytes = Port.BytesToRead;
        byte[] buffer = new byte[bytes];

        Port.Read(buffer, 0, bytes);
        receivingBytes.AddRange(buffer);

        if (!essentialBgWorker.IsBusy)
        {
            receivedBytes.AddRange(receivingBytes);
            receivingBytes.Clear();
            essentialBgWorker.RunWorkerAsync();
        }
    }
 }

private void essentialBgWorker_DoWork(object sender, DoWorkEventArgs e)
{
    foreach (byte hexByte in receivedBytes)
    {
        //color = Color.Gray;
        if ((Config.Data.Serial_DisplayLevel == GLOBAL.HEX_LEVEL_NONE))
        {
            if ((hexByte == '\n') || ((hexByte >= 0x20) && (hexByte <= 0x7E)))
            {
                String tmpString = new String((char)hexByte, 1);
                //essentialBgWorker.ReportProgress(0, tmpString);
                //in here i am putting in the log file and appending in the richtext box
                PreprocessAppend(tmpString, Color.Black, false);
            }
        }
    }

    process.ProcessData(receivedBytes);//in here i am processing the data
    receivedBytes.Clear();
}


private void essentialBgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    string str = e.UserState.ToString();
    logWriter.Write(str);
    logWriter.Flush();

    if (e.ProgressPercentage == 0)
    {
        AppendSerial(str, Color.Black, false);
    }
    else if (e.ProgressPercentage == 1)
    {
        AppendSerial(str, Color.Black, false);
    }
}


SerialTab.SerialPort.AppendSerial += delegate (string data, Color txtColor, bool newLineCheck)
{
    this.BeginInvoke((MethodInvoker)(delegate ()
    {
        if (newLineCheck && (serialTextBox.Text != "") && (serialTextBox.Text[serialTextBox.TextLength - 1] != '\r') && (serialTextBox.Text[serialTextBox.TextLength - 1] != '\n'))
        {
            data = "\n" + data;
        }

        AppendTextbox(serialTextBox, data, txtColor);
    }));
};

void AppendTextbox(RichTextBox tb, string data, Color txtColor)
{
    if (data == null)
    {
        return;
    }

    int start = tb.TextLength;
    tb.AppendText(data);
    int end = tb.TextLength;

    // Textbox may transform chars, so (end-start) != text.Length
    tb.Select(start, end - start);
    tb.SelectionColor = txtColor;

    //reset color to defaults
    tb.SelectionLength = 0;
    tb.SelectionColor = serialTextBox.ForeColor;

    //move caret to bottom of page
    ScrollToBottom(tb);

    //ensure text buffer stays below 15000 characters
    checkTextBoxLength(tb);
}

void checkTextBoxLength(RichTextBox box)
{
    //ensure text buffer in text box gets too large
    if (box.Text.Length > 15000)
    {
        box.ReadOnly = false;
        box.SelectionStart = 0;
        box.SelectionLength = box.TextLength - 10000;
        box.SelectedText = "";
        box.ReadOnly = true;
    }
}

Ответы [ 3 ]

0 голосов
/ 29 мая 2019

Я рекомендую вам испортить весь существующий код и продолжить использовать что-то вроде этого, полученное из примера из MSDN

namespace WindowsFormsApp3
{
    public partial class Form1 : Form
    {

        SerialPort mySerialPort = new SerialPort("COM1");

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            mySerialPort.BaudRate = 9600;
            mySerialPort.Parity = Parity.None;
            mySerialPort.StopBits = StopBits.One;
            mySerialPort.DataBits = 8;
            mySerialPort.Handshake = Handshake.None;
            mySerialPort.RtsEnable = true;

            mySerialPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);

            mySerialPort.Open();


        }

        private void DataReceivedHandler(
                        object sender,
                        SerialDataReceivedEventArgs e)
        {
            SerialPort sp = (SerialPort)sender;
            string indata = sp.ReadExisting();

            textBox1.InvokeIfRequired(ctrl => ctrl.AppendText(indata));
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            mySerialPort.Close();
        }

    }

    public static class ControlHelpers
    {
        public static void InvokeIfRequired<T>(this T control, Action<T> action) where T : ISynchronizeInvoke
        {
            if (control.InvokeRequired)
            {
                control.Invoke(new Action(() => action(control)), null);
            }
            else
            {
                action(control);
            }
        }
    }
}

Ваш обработчик DataReceived довольно сложен. Пример MSDN использует гораздо более простой интерфейс, где он заботится о чтении, буферизации и т. Д. С помощью ReadExisting. Возможно, вы захотите присоединиться к нему и просто добавлять полученные данные в какой-то буфер (stringbuilder?) И регулярно проверять буфер, чтобы убедиться, что он содержит полное сообщение, которое вы хотите обработать

0 голосов
/ 31 мая 2019

Я вижу, что вы пытаетесь сделать, и у вас вроде есть правильная идея, я думаю, что у вас просто проблема с многопоточностью.У вас есть код для вашего PreprocessAppend метода?Вам, вероятно, нужно использовать BeginInvoke в этой функции, и это должно решить вашу проблему.

Лично я не думаю, что вам нужен фоновый работник, так как событие Serial DataReceived является многопоточным.Я бы порекомендовал то, что сказал другой ответ, и использовал бы ReadExisting, так как он соберет все символы в полученном буфере, и вы сможете определить, что делать дальше.Просто будьте осторожны, потому что событие DataReceived может сработать всякий раз, когда оно решит, что это хорошее время, поэтому вы можете получить только частичную строку назад.Если вы пойдете по этому пути, вам придется строить свою строку, пока вы не узнаете, что у вас есть все это, а затем обработать ее.

В моем примере ниже я ожидаю перевод строки в качестве моего терминатора, поэтому я собираюстрока, пока у меня не будет всей строки, затем я ее обработаю.

//private variables
private SerialPort sp;
private StringBuilder sb;
private char LF = (char)10;

//function to initialize all my objects
public void init_state_machine()
{
    sp = new SerialPort(currentSettings.ComPort, currentSettings.BaudRate);
    sp.DataReceived += new SerialDataReceivedEventHandler(sp_DataReceived);

    sb = new StringBuilder();
    sb.Clear();
}

private void sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    string currentLine = "";
    string Data = sp.ReadExisting();

    foreach (char c in Data)
    {
        if (c == LF)
        {
            sb.Append(c);

            //because it's threaded its possible to enter this code while processing so we will
            //clear our string building immediately
            currentLine = sb.ToString();
            sb.Clear();

            //process your line here or whatever
            processReceivedData(currentLine);
        }
        else
        {
            sb.Append(c);
        }
    }
}

//this is where you process the response.  For a simple example I just append the string
//to our textbox, but you could do any computations in here.
private void processReceivedData(string s)
{
    this.BeginInvoke((MethodInvoker)delegate 
    {
        serialTextBox.Text += s;
    });
}
0 голосов
/ 29 мая 2019

Не обращайтесь к элементам управления windows из любого потока, кроме того, который их создал. При использовании BackgroundWorker:

  • добавить фонового дизайнера в конструктор форм
  • установить для свойства ReportsProgress значение true
  • присоединить обработчик событий к DoWork, а другой к ProgressChanged
  • в событии DoWork, выполните фоновую работу, такую ​​как чтение с последовательного порта. Каждый раз, когда вы хотите обновить элемент управления Windows, вызовите метод backgroundworker ReportProgress () с аргументом объекта для пользовательского состояния, содержащего некоторые данные, которые вы хотите использовать в элементе управления
  • Я обычно использую progress int для указания чего-либо, например запуска оператора switch для выбора одного из нескольких текстовых полей для обновления

Фоновый рабочий правильно обеспечит выполнение события ProgressChanged потоком пользовательского интерфейса, что означает, что вы можете обновить элементы управления из него

Глядя на ваш код, кажется, вы шли по этому пути, потому что я вижу закомментированный вызов ReportProgress. Показать содержимое вашего обработчика событий ProgressChanged. Убедитесь, что единственный раз, когда вы получаете доступ к любому элементу управления Windows (чтение или запись любого свойства или вызов любого метода), находится в ProgressChanged, а не в DoWork


Редактировать / Update

Теперь я могу видеть больше вашего кода, похоже, вы встали на путь, делающий его слишком сложным. Я сделаю еще один ответ

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...