我试图在主线程中调用一个方法来更新多个UI元素。其中一个元素是RichTextView
。我找到了3个更新UI的方法,其中所有3个在运行一段时间后崩溃,出现以下错误。只要我将RichTextView更改为简单文本框,类型2就不会再崩溃(我还不确定是否是这种情况)。
未处理的类型' System.StackOverflowException' 发生在System.Windows.Forms.dll
中
我的简化代码
// Type 1
private readonly SynchronizationContext synchronizationContext;
public Form1() {
InitializeComponent();
// Type 1
synchronizationContext = SynchronizationContext.Current;
}
//Type 3
public void Log1(object message) {
Invoke(new Log1(Log), message);
}
public void Log(object message) {
if (this.IsDisposed || edtLog.IsDisposed)
return;
edtLog.AppendText(message.ToString() + "\n");
edtLog.ScrollToCaret();
Application.DoEvents();
}
private void btnStart_Click(object sender, EventArgs e) {
for (int i = 0; i < 10000; i++) {
ThreadPool.QueueUserWorkItem(Work, i);
}
Log("Done Adding");
}
private void Work(object ItemID) {
int s = new Random().Next(10, 15);// Will generate same random number in different thread occasionally
string message = ItemID + "\t" + Thread.CurrentThread.ManagedThreadId + ",\tRND " + s;
//Type1
synchronizationContext.Post(Log, message);
// Type 2
//Invoke(new Log1(Log), message);
// Type 3
//Log1(message);
Thread.Sleep(s);
}
完整代码
using System;
using System.Threading;
using System.Windows.Forms;
namespace Test {
public delegate void Log1(string a);
public partial class Form1 : Form {
private System.ComponentModel.IContainer components = null;
private Button btnStart;
private TextBox edtLog;
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
private void InitializeComponent() {
this.btnStart = new Button();
this.edtLog = new TextBox();
this.SuspendLayout();
this.btnStart.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
this.btnStart.Location = new System.Drawing.Point(788, 12);
this.btnStart.Size = new System.Drawing.Size(75, 23);
this.btnStart.Text = "Start";
this.btnStart.Click += new System.EventHandler(this.btnStart_Click);
this.edtLog.Anchor = (AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right);
this.edtLog.Location = new System.Drawing.Point(12, 41);
this.edtLog.Multiline = true;
this.edtLog.ScrollBars = ScrollBars.Vertical;
this.edtLog.Size = new System.Drawing.Size(851, 441);
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.ClientSize = new System.Drawing.Size(875, 494);
this.Controls.Add(this.edtLog);
this.Controls.Add(this.btnStart);
this.ResumeLayout(false);
this.PerformLayout();
}
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
// Type 1
private readonly SynchronizationContext synchronizationContext;
public Form1() {
InitializeComponent();
// Type 1
synchronizationContext = SynchronizationContext.Current;
}
//Type 3
public void Log1(object message) {
Invoke(new Log1(Log), message);
}
public void Log(object message) {
if (this.IsDisposed || edtLog.IsDisposed)
return;
edtLog.AppendText(message.ToString() + "\n");
edtLog.ScrollToCaret();
Application.DoEvents();
}
private void btnStart_Click(object sender, EventArgs e) {
for (int i = 0; i < 10000; i++) {
ThreadPool.QueueUserWorkItem(Work, i);
}
Log("Done Adding");
}
private void Work(object ItemID) {
int s = new Random().Next(10, 15);// Will generate same random number in different thread occasionally
string message = ItemID + "\t" + Thread.CurrentThread.ManagedThreadId + ",\tRND " + s;
//Type1
synchronizationContext.Post(Log, message);
// Type 2
//Invoke(new Log1(Log), message);
// Type 3
//Log1(message);
Thread.Sleep(s);
}
}
}
问题1
为什么以及何时应该使用SynchronizationContext
或Invoke
。有什么区别(纠正我,如果我错了,因为我在winform上运行,SynchronizationContext.Current
总是存在)?
问题2 为什么我在这里收到StackOverflow错误?难道我做错了什么?在单独的方法或同一个worker方法中调用invoke之间有什么区别(Log1在直接调用Invoke时崩溃)
问题3
当用户在线程完成作业之前关闭应用程序时,我得到并且异常说Form1被释放并且在调用Log方法(任何3种类型)时不可访问。我应该处理线程中的异常(包括主线程)吗?
答案 0 :(得分:1)
Application.DoEvents();
这种方法如何使程序以完全不可思议的方式失败,真是令人印象深刻。你需要的基本配方是一个非常容易的方法。您需要一个以高速率调用Log1()的工作线程,大约每秒一千次或更多。并且在UI线程上运行的代码执行的操作非常昂贵,耗时超过一毫秒。就像在TextBox中添加一行一样。
然后:
您添加了Application.DoEvents(),因为您注意到您的UI冻结了,文本框没有显示添加的文本行,5秒后您收到“无响应!”鬼窗。是的,它解决了这个问题。但是,正如你所发现的那样,不是以非常有建设性的方式。您想要它要做的是为文本框分派Paint事件。 DoEvents()不够有选择性,它会所有事件。包括你没有指望的那些,由Invoke()触发的事件。只是选择它:
edtLog.Update();
不再有StackOverflowException。但仍然不是一个有效的程序。你会得到一个疯狂的滚动文本框,没有人可以阅读。而你也无法阻止它,程序仍然没有用户输入,所以点击关闭按钮不起作用。
您尚未解决程序中的基本错误。消防水带虫,是种族和死锁后第3种最常见的线程错误。您的工作线程以远的速率生成结果,高于UI线程可以使用的速率。最重要的是,人类可以看到它们的速度。您已经创建了一个无法使用的用户界面。
解决这个问题,你现在从线程中获得的所有痛苦都将消失。代码太虚假,无法推荐特定修复,但应该只在某处显示您每秒拍摄一次的快照。或者仅在工作线程完成时更新UI,这是BackgroundWorker鼓励的模式。无论如何重新平衡工作,以便UI线程比工作线程做的工作少。