Добавлять текст в поле расширенного текста из цикла while, который выполняет непрерывное чтение TcpClient - PullRequest
0 голосов
/ 05 марта 2019

Я пытаюсь настроить программу, которая доступна для чтения входящих пакетов данных из TcpClient. Идея состоит в том, чтобы выполнять непрерывное чтение данных в цикле while и отображать их в элементе rich text box. В качестве примера я пытался настроить свою программу следующим образом (часть для чтения tcp клиента опущена, чтобы уменьшить объем кода):

program.cs - главная точка входа:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Testing
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}

Класс, в котором настроен цикл while:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace Testing
{
    class Whiler
    {        
        public static void Stremer()
        {
            Thread Streamer = new Thread(OutPutFromWhile);
            Streamer.Start();
            OutPutFromWhile();
        }

        public static void OutPutFromWhile()
        {
            int i = 0;
            Form1 mybox = new Form1();
            // I want to display all i values from this while loop in the textbox
            while (true)
            {
                i++;
                mybox.richTextBox1.AppendText(i.ToString() + Environment.NewLine);
            }
        }
    }
}

Form1.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Testing
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Whiler.Stremer();
        }
    }
}

Forms1.Designer.cs

namespace Testing
{
    partial class Form1
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.richTextBox1 = new System.Windows.Forms.RichTextBox();
            this.button1 = new System.Windows.Forms.Button();
            this.SuspendLayout();
            // 
            // richTextBox1
            // 
            this.richTextBox1.Location = new System.Drawing.Point(56, 103);
            this.richTextBox1.Name = "richTextBox1";
            this.richTextBox1.Size = new System.Drawing.Size(659, 280);
            this.richTextBox1.TabIndex = 0;
            this.richTextBox1.Text = "";
            // 
            // button1
            // 
            this.button1.Location = new System.Drawing.Point(56, 29);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(658, 52);
            this.button1.TabIndex = 1;
            this.button1.Text = "button1";
            this.button1.UseVisualStyleBackColor = true;
            this.button1.Click += new System.EventHandler(this.button1_Click);
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(800, 450);
            this.Controls.Add(this.button1);
            this.Controls.Add(this.richTextBox1);
            this.Name = "Form1";
            this.Text = "Form1";
            this.ResumeLayout(false);

        }

        #endregion

        public System.Windows.Forms.RichTextBox richTextBox1;
        private System.Windows.Forms.Button button1;
    }
}

Ответы [ 2 ]

2 голосов
/ 06 марта 2019

Поток пользовательского интерфейса - единственный поток, который может выполнять действия пользовательского интерфейса!
Одна из проблем, с которой вы столкнетесь, заключается в том, что не-пользовательский поток попытается обновить пользовательский интерфейс. Это может делать только пользовательский интерфейс.

В вашем случае рассмотрите возможность использования BackgroundWorker для чтения.

Вы можете решить создать класс, производный от BackgroundWorker, для своей работы. Однако, поскольку ваш BackgroundWorker не обладает большой функциональностью, так же просто использовать стандарт BackGroundWorker и его событие DoWork.

Используйте дизайнер окон Windows для добавления BackgroundWorker. Измените свойства в окне свойств.

WorkerReportsProgress = true;
WorkerSupportsCancel = true;

Реагировать на события:

DoWork = DoBackGroundWork
ProgressChanged = ReportProgress
RunWorkerCompleted = ReportBackgroundWorkCompleted

Или альтернативно используйте конструктор:

private readonly BackGroundWorker backgroundWorker;
public Form1()
{
    InitializeComponent();

    this.backgroundWorker = new BackgroundWorker
    {
        WorkerReportsProgress = true,
        WorkerSupportsCancellation = true,
    };

    // Subscribe to events:
    backgroundWorker.DoWork += this.DoBackGroundWork;
    backgroundWorker.ProgressChanged += this.OnReportProgress;
    backgroundWorker.RunWorkerCompleted += this.ReportBackgroundWorkCompleted;

    // make sure that the BackgroundWorker is properly disposed if this form is disposed:
    if (this.components == null) this.components = new System.ComponentModel.Container();
    this.components.Add(this.backgroundWorker);
}

DoBackgroundWork - это функция события, которая выполняет фоновую работу. Функция выполняется фоновым работником. Это не поток пользовательского интерфейса. Не делайте ничего в этой форме. Делайте то, что нужно вашему фоновому работнику. Когда у него есть данные, которые необходимо отобразить, звоните ReportProgress. Регулярно проверяйте CancellationPending, чтобы увидеть, должно ли оно перестать работать

private void DoBackGroundWork(object sender, DoWorkEventArgs e)
{
    var backgroundWorker = (BackgroundWorker)sender;
    While(!backgroundWorker.CancellationPending)
    {
        // continue producing output
        var producedOutput = ...
        // report that new output is available:
        backgroundWorker.ReportProgress(0, producedOutput);
    }
}

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

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

private void OnReportProgress(object sender, ProgressChangedEventArgs e)
{
    // you know the type that is reported as progress,
    // it is the type of the produced output
    string producedText = (string)e.UserState;
    this.AddToRichTextBox(producedText);
}

ReportBackgroundWorkCompleted требуется только в том случае, если вам нужно что-то сделать, если фоновый работник завершит работу. Он выполняется потоком пользовательского интерфейса

private void ReportBackgroundWorkCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    // finished background working. Do some cleanup
    this.ShowBackgroundWorkerActive(false); // for example: hide ajax loader gif
}

Использование фонового рабочего :

void ShowBackGroundWorkerActive(bool active)
{
    // give user indication about active backgroundworker, for instance show ajax loader gif
    this.GifBackgroundWorkerActive.Visible = active;
}

bool IsBackGroundWorkerActive => this.GifBackgroundWorkerActive.Visible;

void StartBackgroundWorking()
{
     if (this.IsBackgroundWorkerActive) return; // already active

     // do some preparations:
     this.ShowBackgroundWorkerActive(true);

     // start the backgroundworker
     this.backgroundWorker.RunWorkerAsync();
}
void CancelBackgroundWorking()
{
    this.backgroundWorker.CancelAsync();
}

Перестать работать с фоном, если форма закрыта

Единственное, что вам нужно сделать, это убедиться, что ваше окно не закрыто активным фоновым рабочим. Для этого используйте событие OnFormClosing.

bool formClosingRequested = false;
void OnFormClosing(object sender, FormClosingEventArgs e)
{
    if (this.BackGroundWorkerActive)
    {
        // can't close right now: need to stop the backgroundWorker first.
        // remember that we want to close the form:
        this.formClosingRequested = true;
        this.CancelBackgroundWorking();
        e.Cancel = true;
    }
    // else, no reason to cancel closing
}

Когда фоновый работник будет завершен, после запроса закрытия, нам придется закрыть форму.

void ReportBackgroundWorkCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    // finished background working. Do some cleanup
    this.ShowBackgroundWorkerActive(false);

    if (this.formClosingRequested)
    {
       // Close the form. This will lead to a FormClosingEvent
       // but this time the background worker won't be active
       this.Close();
    }
}
1 голос
/ 05 марта 2019

Элементы управления пользовательского интерфейса действительно должны быть доступны только из потока пользовательского интерфейса. Это можно сделать с помощью метода winforms BeginInvoke, который будет помещать указанный код в очередь потока пользовательского интерфейса. используя ваш пример кода ...

while (true)
{
    i++;
    mybox.BeginInvoke ((MethodInvoker) delegate
    {
        mybox.richTextBox1.AppendText(i.ToString() + Environment.NewLine);
    });
}

это решит проблему доступа.

Обратите внимание, что вы все еще можете повесить поток пользовательского интерфейса, заполнив его BeginInvokes, что, вероятно, и сделает этот узкий цикл. Я предполагаю, что это всего лишь пример, и данные по TCP будут поступать гораздо медленнее и реже. Если нет, то вам, возможно, придется переосмыслить это и, возможно, выполнить пакетную обработку или обновить только через определенные промежутки времени.

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