Richtexbox 如何在 Winforms 上附加来自事件侦听器的异步文本?

Posted

技术标签:

【中文标题】Richtexbox 如何在 Winforms 上附加来自事件侦听器的异步文本?【英文标题】:How richtexbox can append text async from event listener on winforms? 【发布时间】:2021-12-30 00:03:20 【问题描述】:

如何异步处理richtextbox?例如,我有一个类和一个记录某种计算的事件

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

namespace ExampleProject

    public partial class Form1 : Form
    
        public Form1()
        
            InitializeComponent();
        

        private void button1_Click(object sender, EventArgs e)
           
            //I subscribe to an event that often sends messages 
            //and I want to display
            var c = new Core();
            c.Notify += DisplayMessage;
            c.ExampleMethod();
        

        private void DisplayMessage(object sender, LogEventArgs e)
            //When a event arrives, transfer to richTextBox1
            richTextBox1.AppendText("\r\n" + e.Date.ToString("dd/MM/yyyy HH:mm:ss "), Color.SlateGray);
            richTextBox1.AppendText(e.Message, Color.Blue);
            richTextBox1.SelectionStart = richTextBox1.Text.Length;
            richTextBox1.ScrollToCaret();
            
        
    

    class Core
    
        public delegate void LogHandler(object sender, LogEventArgs e);
        public event LogHandler Notify;

        //
        public void ExampleMethod()
           
            //generate messages with a pause in a random value
            var rand = new Random();
            
            for (int i = 1; i < 10000; i++)
               var pause = rand.Next(50, 2000);
                Thread.Sleep(pause);
                Notify?.Invoke(this, new LogEventArgs($"logged i pause in miliseconds pause", DateTime.Now, MessageType.Notice));
            
        
       //The class in which log messages are placed
        class LogEventArgs
    

        public string Message  get; 

        public DateTime Date  get; 
        public MessageType MessageType  get; 


        public LogEventArgs(string mes, DateTime date, MessageType messageType)
        
            Message = mes;
            Date = date;
            MessageType = messageType;
        
    

    enum MessageType
    
        Notice,
        Warning,
        Error,
    
     //Extension method to set line colors in RichTextBox
    public static class RichTextBoxExtensions
    
        public static void AppendText(this RichTextBox box, string text, Color color)
        
            box.SelectionStart = box.TextLength;
            box.SelectionLength = 0;

            box.SelectionColor = color;
            box.AppendText(text);
            box.SelectionColor = box.ForeColor;
        
    



尝试了 backgroundworker、task、synchronization 的选项......但没有得到任何结果。我很惊讶 WinForms 已经存在很多年了,而微软还没有向它们添加异步操作,例如AppendTextAsync()。如何强制将文本异步添加到富文本框?

【问题讨论】:

有什么好处? @CaiusJard 我正在处理数据,它们并不大,有 10-20k 行,如果我在 Richtextbox 中显示日志,那么我的应用程序只会挂起 5-10 分钟 你是说在富文本框中添加 20000 行需要 10 分钟才能完成? 能否放个完整的代码复现?一个完整的小项目/完整的 Form1.cs 我们可以粘贴到一个新项目上,例如在表单中添加一个丰富的/文本框? 在竞争和死锁之后,第三个最常见的线程错误是 firehose 错误。当生产者线程生成数据的速度快于消费者线程处理数据的速度时,就会发生这种情况。当消费者是 Winforms 应用程序的 UI 线程时非常明显,它停止处理其正常职责(如绘制和响应用户输入),拼命地试图跟上 Begin/Invoke 请求并且无法赶上.需要通过减少调用频率来解决,每秒 25 次足够快,足以让人眼保持忙碌。 【参考方案1】:

在表单上添加另一个文本框

将此代码粘贴到 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;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace ExampleProject

    public partial class Form1 : Form
    
        public Form1()
        
            InitializeComponent();
        

        private async void button1_Click(object sender, EventArgs e)
        
            var c = new Core();
            c.Notify += DisplayMessage;
            await c.ExampleMethod();
        

        private void DisplayMessage(object sender, LogEventArgs e)
        
            richTextBox1.AppendText("\r\n" + e.Date.ToString("dd/MM/yyyy HH:mm:ss "), Color.SlateGray);
            richTextBox1.AppendText(e.Message, Color.Blue);
            richTextBox1.SelectionStart = richTextBox1.Text.Length;
            richTextBox1.ScrollToCaret();
            
        
    

    class Core
    
        public delegate void LogHandler(object sender, LogEventArgs e);
        public event LogHandler Notify;


        public async Task ExampleMethod()
        
            var rand = new Random();

            for (int i = 1; i < 10000; i++)
               var pause = rand.Next(50, 200);
                await Task.Delay(pause);
                Notify?.Invoke(this, new LogEventArgs($"logged i pause in miliseconds pause", DateTime.Now, MessageType.Notice));
            
        
    
        class LogEventArgs
    

        public string Message  get; 

        public DateTime Date  get; 
        public MessageType MessageType  get; 


        public LogEventArgs(string mes, DateTime date, MessageType messageType)
        
            Message = mes;
            Date = date;
            MessageType = messageType;
        
    

    enum MessageType
    
        Notice,
        Warning,
        Error,
    
    public static class RichTextBoxExtensions
    
        public static void AppendText(this RichTextBox box, string text, Color color)
        
            box.SelectionStart = box.TextLength;
            box.SelectionLength = 0;

            box.SelectionColor = color;
            box.AppendText(text);
            box.SelectionColor = box.ForeColor;
        
    




运行它,单击按钮,然后继续在另一个文本框中输入 - 它保持响应等。我不知道您需要等待多长时间才能看到主要的减速,因为 RTB 中的文本变得巨大.. 但实际上您应该定期查看您的日志,这样您就不会在 RTB 中建立数兆字节的文本

winforms 中的异步并不是要使用某种异步方式更新控件;控件只能由创建它们的线程访问,因此与控件的任何交互我们通常要求该线程执行它(但我认为这是一个透视问题:如果后台线程正在要求 UI 线程更新控件,那么它是与后台线程正在执行的任何工作异步完成)-确保您对它们进行的访问是快速的,使用异步方式进行繁重的工作(API 调用?),然后调用以让您的 UI 线程更新控件。

【讨论】:

非常感谢您的帮助。在实际代码中,我的Task 中包含了其他几个同步方法。我认为将顶部方法包装在 Task 中就足够了,一切都会正常工作,但是 This async method lacks 'await' operators and will run synchronously 这取决于它们是什么,但该消息并非来自使用任务;它来自使用 async 修饰符标记方法,然后在方法主体中不执行任何 await 操作

以上是关于Richtexbox 如何在 Winforms 上附加来自事件侦听器的异步文本?的主要内容,如果未能解决你的问题,请参考以下文章

以流方式读写文件:文件菜单打开一个文件,文件内容显示在RichTexBox中,执行复制剪切粘贴后,通过文件菜单可以保存修改后的文件。

如何将 BlockUIContainer 中 RichTextBox 的内容保存到 RTF 文件?

如何在 Linux 上运行 .Net 核心 WinForms 应用程序

Winforms:如何加速 Invalidate()?

如何重置 WinForms 应用程序上的选项卡索引?

WinForms中常见的“拖放”图标如何实现