子线程中循环操作UI线程时导致子线程无法正常结束的问题总结

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/tiana0/article/details/23115295

鄙人在多线程开发过程中遇到一个问题:代码中的子线程需要在循环中不断操作UI线程,但是在关闭UI窗口时,却提示“无法访问已释放的对象”。

为了重现这个问题,本文使用多线程实现了一个简易的“小时钟”,时钟能够动态地显示当前的系统时间,时间更新间隔为1s。当然,我们完全可以使用Timer来实现这样一个“小时钟”,但是为了说明问题,我就勉强的使用多线程吧,就算有点“小题大做”之嫌,也请大家将就一下。

程序的运行效果如下图所示。

 

下面,先给出存在问题的问题代码。

using System;
using System.Threading;
using System.Windows.Forms;
 
namespace InvokeExp
{
    public partial class frmInvokeExp : Form
    {
        Thread t;
        public frmInvokeExp()
        {
 
            InitializeComponent();
            this.lblCurrentName.Text = System.DateTime.Now.ToString();
        }
        private void DoWork()
        {
            while (true)
            {
                Thread.Sleep(1000);
                this.Invoke(
                    new Action(() => { this.lblCurrentName.Text = System.DateTime.Now.ToString(); })
                    );
            }
        }
 
        private void frmInvokeExp_Load(object sender, EventArgs e)
        {
            t = new Thread(new ThreadStart(DoWork));
            t.Start();
        }
    }
}
调试中,程序运行后,点击窗口右上角的关闭按钮退出程序,便会提示“无法访问已释放的对象”,定位异常,异常出现在子线程操作 UI 线程的代码块(如下所示)。

this.Invoke(
                new Action(() => { this.lblCurrentName.Text = System.DateTime.Now.ToString(); })
           );

从异常信息中很容易了解到,关闭窗口后,UI线程已经被释放,而我们的子线程并没有被正常关闭,仍然在执行,当执行到操作UI线程的代码时,出现了此问题。

因为想到,将线程的IsBackground属性设置为true时,线程会随着主线程一起退出,而IsBackground设置为false的线程需要等到自己的工作都执行完成后才会退出,但是此处的代码很显然是执行不完自己的工作的(死循环)。另外,.net中新开启的线程的IsBackground属性默认被设置为false。想到这些后,增加属性设置代码“t.IsBackground = true;”,再次运行程序后关闭窗口,异常不再,OK啦。

但是自古“贱人”多矫情(开个玩笑),手动将“Thread.Sleep(1000);”修改为“Thread.Sleep(10);”,发现问题又出现了,细心一想,应该是由于窗体关闭的过程需要持续一定的时间,恰巧在某一时刻,主线程已经退出,但是子线程并未退出仍在运行且碰巧执行到代码“this.Invoke(...)”,便产生异常,只要Sleep时间能避开这种情况,程序就能正常退出了。但是这个时间设置多少好呢,没有定论,所以总感觉单纯通过设置IsBackground属性来解决这个问题并不是很靠谱。

接着又想到,在窗体的关闭事件中关闭子线程的方法,于是在窗体的关闭事件中增加关闭线程的代码。

private void frmInvokeExp_FormClosing(object sender, FormClosingEventArgs e)
{
     t.Abort();
}

再次调试程序,一切正常,就算注释掉代码“Thread.Sleep(1000);”也没有任何问题了。至于为什么没出现像修改线程的IsBackground属性方法时的情况(修改Sleep时间也可能出现异常的情况),我也说不清楚,不知道有没有哪位兄台对这个有研究,望不吝赐教


猜你喜欢

转载自blog.csdn.net/tiana0/article/details/23115295