从while循环的内部将丰富文本框中的文本追加到执行TcpClient的连续读取中

时间:2019-03-05 16:06:55

标签: c# multithreading winforms

我正在尝试建立一个程序,该程序可用于从TcpClient读取传入的数据包。这个想法是在while循环中连续读取数据并将其显示在RTF文本框元素中。举例来说,我一直试图像这样设置程序(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 个答案:

答案 0 :(得分:2)

UI线程是唯一可以执行UI操作的线程!
您将遇到的问题之一是非UI线程将尝试更新用户界面。只有UI线程可以执行此操作。

您可以考虑使用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 是执行后台工作的事件函数。该功能由后台工作者执行。它不是UI线程。请勿在此表单上做任何事情。执行后台工作者需要做的事情当必须显示某些数据时,请致电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中的第一个参数是一个数字,指示此后台线程的进度。 UI可以使用它来填充进度栏。由于您不知道将要生成多长时间,因此无法填写正确的数字。

当后台工作人员报告进度时,会调用

OnReportProgress 此功能由UI线程执行。随意做与UI相关的任何事情

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 仅在后台工作人员完成后需要执行某些操作时才需要。它由UI线程执行

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 :(得分:1)

UI控件确实仅需要从UI线程访问。这可以使用winforms BeginInvoke方法完成,该方法会将指定的代码推送到UI线程的队列中。使用您的示例代码...

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

这将解决访问问题。

但是请注意,您仍然可以通过使用BeginInvokes充斥UI线程来挂起UI线程,此紧密循环可能会这样做。我以为这只是一个例子,TCP数据的传输速度会慢得多,频率也会降低。如果没有,那么您可能需要重新考虑并可能将其批处理,或者仅在特定时间间隔后更新。