SynchronizationContext或Invoke

时间:2015-10-04 07:43:10

标签: .net multithreading visual-studio-2010 c#-4.0

我试图在主线程中调用一个方法来更新多个UI元素。其中一个元素是RichTextView。我找到了3个更新UI的方法,其中所有3个在运行一段时间后崩溃,出现以下错误。只要我将RichTextView更改为简单文本框,类型2就不会再崩溃(我还不确定是否是这种情况)。

  

未处理的类型' System.StackOverflowException'   发生在System.Windows.Forms.dll

StackOverflowException

我的简化代码

    // 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

为什么以及何时应该使用SynchronizationContextInvoke。有什么区别(纠正我,如果我错了,因为我在winform上运行,SynchronizationContext.Current总是存在)?

问题2 为什么我在这里收到StackOverflow错误?难道我做错了什么?在单独的方法或同一个worker方法中调用invoke之间有什么区别(Log1在直接调用Invoke时崩溃)

问题3

当用户在线程完成作业之前关闭应用程序时,我得到并且异常说Form1被释放并且在调用Log方法(任何3种类型)时不可访问。我应该处理线程中的异常(包括主线程)吗?

1 个答案:

答案 0 :(得分:1)

   Application.DoEvents();

这种方法如何使程序以完全不可思议的方式失败,真是令人印象深刻。你需要的基本配方是一个非常容易的方法。您需要一个以高速率调用Log1()的工作线程,大约每秒一千次或更多。并且在UI线程上运行的代码执行的操作非常昂贵,耗时超过一毫秒。就像在TextBox中添加一行一样。

然后:

  • Log1()调用Log()调用DoEvents()。
  • 允许另一个Log1()调用调用调用DoEvents的Log()。
  • 允许另一个Log1()调用调用调用DoEvents的Log()。
  • 允许另一个Log1()调用调用调用DoEvents的Log()。
  • 允许另一个Log1()调用调用调用DoEvents的Log()。
  • ....
  • KABOOM!

您添加了Application.DoEvents(),因为您注意到您的UI冻结了,文本框没有显示添加的文本行,5秒后您收到“无响应!”鬼窗。是的,它解决了这个问题。但是,正如你所发现的那样,不是以非常有建设性的方式。您想要它要做的是为文本框分派Paint事件。 DoEvents()不够有选择性,它会所有事件。包括你没有指望的那些,由Invoke()触发的事件。只是选择它:

  edtLog.Update();

不再有StackOverflowException。但仍然不是一个有效的程序。你会得到一个疯狂的滚动文本框,没有人可以阅读。而你也无法阻止它,程序仍然没有用户输入,所以点击关闭按钮不起作用。

您尚未解决程序中的基本错误。消防水带虫,是种族和死锁后第3种最常见的线程错误。您的工作线程以的速率生成结果,高于UI线程可以使用的速率。最重要的是,人类可以看到它们的速度。您已经创建了一个无法使用的用户界面。

解决这个问题,你现在从线程中获得的所有痛苦都将消失。代码太虚假,无法推荐特定修复,但应该只在某处显示您每秒拍摄一次的快照。或者仅在工作线程完成时更新UI,这是BackgroundWorker鼓励的模式。无论如何重新平衡工作,以便UI线程比工作线程做的工作少。