You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# WinForms中Console.WriteLine无法更新RichTextBox问题排查

问题:Console输出重定向至RichTextBox在YouTube上传项目中仅初始消息显示,后续无更新

我已经查阅了所有同类问题,但仍未找到自身场景失效的原因。测试程序中,通过自定义TextBoxStreamWriter类将Console.WriteLine输出重定向至RichTextBox,按钮点击时可正常显示内容;但在基于YouTube API 3开发的上传项目中,仅初始的「uploading video」消息能显示在RichTextBox,后续上传进度、错误信息、视频ID的Console输出均无法更新控件,上传功能可正常完成。


测试类代码

public class TextBoxStreamWriter : TextWriter
{
    RichTextBox _output = null;

    public TextBoxStreamWriter(RichTextBox output)
    {
        _output = output;
    }

    public override void Write(char value)
    {
        base.Write(value);
        _output.AppendText(value.ToString()); // When character data is written, append it to the text box.
    }

    public override Encoding Encoding
    {
        get { return System.Text.Encoding.UTF8; }
    }
}

测试窗体代码

public partial class Form1 : Form
{
   
    TextWriter _writer = null;

    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        _writer = new TextBoxStreamWriter(richTextBox1);

        // Redirect the out Console stream
        Console.SetOut(_writer);

        //Console.WriteLine("Now redirecting output to the text box");
    }

    private void button1_Click(object sender, EventArgs e)
    {
        // Writing to the Console now causes the text to be displayed in the text box.
        Console.WriteLine("Hello world");
    }

}

项目上传功能代码

/// <summary>
/// Upload video to youtube
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button2_Click(object sender, EventArgs e)
{
    //lblstatus.Text = "uploading video";
    Console.WriteLine("uploading video");
    try
    {
        Thread thead = new Thread(() =>
        {
            Run().Wait();
        });
        thead.IsBackground = true;
        thead.Start();

    }
    catch (AggregateException ex)
    {
        Console.WriteLine("Error: " + ex.Message);
        // MessageBox.Show(ex.Message);
    }
}

private async Task Run()
{
    UserCredential credential;
    using (var stream = new FileStream("client_secret", FileMode.Open, FileAccess.Read))
    {
        credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
            GoogleClientSecrets.FromStream(stream).Secrets,
            // This OAuth 2.0 access scope allows an application to upload files to the
            // authenticated user's YouTube channel, but doesn't allow other types of access.
            new[] { YouTubeService.Scope.YoutubeUpload },
            "user",
            CancellationToken.None
        );
    }

    var youtubeService = new YouTubeService(new BaseClientService.Initializer()
    {
        HttpClientInitializer = credential,
        ApplicationName = Assembly.GetExecutingAssembly().GetName().Name
    });

    var video = new Video();
    video.Snippet = new VideoSnippet();
    video.Snippet.Title = txtTitle.Text;
    video.Snippet.Description = txtDescription.Text;
    string[] tagSeo = Regex.Split(txtTagSeo.Text, ","); 
    video.Snippet.Tags = tagSeo;
    video.Snippet.CategoryId = "22";
    video.Status = new VideoStatus();
    video.Status.PrivacyStatus = "public"; // or "private" or "public"
    var filePath = txtPath.Text; // Replace with path to actual movie file.

    Console.WriteLine(filePath);

    using (var fileStream = new FileStream(filePath, FileMode.Open))
    {
        var videosInsertRequest = youtubeService.Videos.Insert(video, "snippet,status", fileStream, "video/*");
        videosInsertRequest.ProgressChanged += videosInsertRequest_ProgressChanged;
        videosInsertRequest.ResponseReceived += videosInsertRequest_ResponseReceived;

        await videosInsertRequest.UploadAsync();
    }
}

void videosInsertRequest_ProgressChanged(Google.Apis.Upload.IUploadProgress progress)
{
    switch (progress.Status)
    {
        case UploadStatus.Uploading:
            Console.WriteLine("{0} bytes sent.", progress.BytesSent);
            //lblstatus.Text = String.Format("{0} bytes sent.", progress.BytesSent);
            break;

        case UploadStatus.Failed:
            //lblstatus.Text = String.Format("An error prevented the upload from completing.{0} ", progress.Exception);
            Console.WriteLine("An error prevented the upload from completing.\n{0}", progress.Exception);
            break;
    }
}

void videosInsertRequest_ResponseReceived(Video video)
{
    Console.WriteLine("Video id '{0}' was successfully uploaded.", video.Id);
    MessageBox.Show("Video id '{0}' was successfully uploaded.", video.Id);
}

问题原因

核心是跨线程访问UI控件:

  • 测试程序中,Console.WriteLine在UI线程(按钮点击事件)执行,直接操作RichTextBox无问题;
  • 上传项目里,后续的Console.WriteLine运行在后台线程(你新开的Thread、YouTube API的回调线程),而WinForms控件仅允许创建它的UI线程访问,跨线程直接操作会导致控件不更新(未触发强制线程检查时,不会抛出异常,但内容无法显示)。

修复方案

1. 让TextBoxStreamWriter支持跨线程更新

修改Write方法,通过Invoke确保在UI线程操作RichTextBox:

public class TextBoxStreamWriter : TextWriter
{
    RichTextBox _output = null;

    public TextBoxStreamWriter(RichTextBox output)
    {
        _output = output;
    }

    public override void Write(char value)
    {
        base.Write(value);
        // 检查是否需要跨线程调用
        if (_output.InvokeRequired)
        {
            _output.Invoke(new Action(() => _output.AppendText(value.ToString())));
        }
        else
        {
            _output.AppendText(value.ToString());
        }
    }

    public override Encoding Encoding
    {
        get { return System.Text.Encoding.UTF8; }
    }
}

2. 优化异步代码写法(可选但推荐)

按钮点击事件里用Thread包裹Run().Wait()的写法冗余,直接用async void(仅适用于事件处理方法)更符合异步规范:

private async void button2_Click(object sender, EventArgs e)
{
    Console.WriteLine("uploading video");
    try
    {
        await Run();
    }
    catch (AggregateException ex)
    {
        Console.WriteLine("Error: " + ex.Message);
    }
}

修改后,所有Console输出无论在哪个线程执行,都会通过UI线程更新RichTextBox,后续的上传进度、结果消息就能正常显示。


内容的提问来源于stack exchange,提问作者user2180706

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 22:50:20