如何有效地结束子线程?

anthony1119 2018-03-09 08:42:53
使用多线程+委托对窗体中的控件进行操作,现在想在关闭窗体的时候终止线程,否则会出现调用已释放的对象的异常,有没有什么好方法呢?
我想到在委托中增加try catch, 一旦捕捉到该异常直接return或者调用Thread.Abort()终止线程,但是感觉这方法不是很好,并且直接return多线程并没有直接结束,而是等到多线程中的方法完全结束后才停止
有什么好的建议呢
另外,通过Thread.Start()开始新开的线程,那这个线程是在什么时候执行呢?
...全文
726 36 打赏 收藏 转发到动态 举报
写回复
用AI写文章
36 条回复
切换为时间正序
请发表友善的回复…
发表回复
sp1234_maJia 2018-03-16
  • 打赏
  • 举报
回复
我们的程序基本上都是这样写的
    public partial class Form1 : Form
    {
        private Thread _thread;
        private bool _isDisposed;
        public Form1()
        {
            InitializeComponent();
        }

        private delegate void ChangeNumber(int i, string time);

        private void SetLabel(int i, string time)
        {
            if (this._isDisposed)
                return;

            this.BeginInvoke((Action)delegate
            {
                label1.Text = time;
                progressBar1.Value = i;
            });
        }

        private void button2_Click(object sender, EventArgs e)
        {
            _thread = new Thread(DoWork);
            _thread.IsBackground = true;
            _thread.Start();
        }

        private void DoWork()
        {
            {
                int a = 1000;
                int b = 0;
                for (int i = 0; i < a; i++)
                {
                    for (int j = 0; j < a; j++)
                    {
                        for (int k = 0; k < a; k++)
                        {
                            b++;
                        }
                    }
                    if (_isDisposed)
                    {
                        return;
                    }
                    SetLabel(i, "Button2: " + DateTime.Now);
                }
            }
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            _isDisposed = true;
        }
也就是说,使用 BeginInvoke 而基本上不使用 Invoke,而且一般不去判断 InvokeRequired。另外并不是非常频繁地去交互显示,交互显示之前使用 _isDisposed 标志是因为此时没有必要去显示了。 当然这只是逻辑上的设计,大多数时候并不需要 _isDisposed 这类标记,有这类标记大都数时候都是因为底层系统有 bug 所以才需要判断。异步处理要使用异步的流程设计,从不阻塞,因此异步代码其实更简洁、也不会纠结出来那么多 bug(异步处理并不会改变你安排好的处理代码执行顺序)
xuzuning 2018-03-16
  • 打赏
  • 举报
回复
我原打算是建议你在向控件赋值之前检查控件是否存在的(Controls.Find("progressBar1", true)) 但既然不会报错,检查就没有必要了
xuzuning 2018-03-16
  • 打赏
  • 举报
回复
引用 20 楼 anthony1119 的回复:
点击界面的按钮,在进度条未满的时候关闭窗体退出程序我说的异常就会出现了
这的不会报错! 即便是我在 button2_Click 方法中删去控件 Controls.Remove(progressBar1); Controls.Remove(label1); 也都不会报错的
  • 打赏
  • 举报
回复
嗯,我改为
            if (this._isDisposed)
                return;
确实是仍然无法防止 Invoke 跟 this.Close 冲突。这个可能算是 Form 对象的特列吧。我们基本上从未用过 Invoke,都是使用 BeginInvoke,而且 BeginInvoke 可以正常运行,可见 Form 的 Close 原本是可以跟线程异步执行正常并发的(Form.Close 并不会真正 GC 释放 Form 对象,它只是做一些内部处理)。所以既然连异步的 BeginInvoke都可以正常执行,那么同步的 Invoke 语句可能是有 bug。
  • 打赏
  • 举报
回复
当使用Invoke 阻塞时,你需要先检测这个 _disposed 标志位然后才能 Invoke:
    public partial class Form1 : Form
    {
        private Thread _thread;
        private bool _isDisposed;
        public Form1()
        {
            InitializeComponent();
        }

        private delegate void ChangeNumber(int i, string time);

        private void SetLabel(int i, string time)
        {
            if (!this._isDisposed)
                return;

            if (this.InvokeRequired)
            {
                ChangeNumber cn = SetLabel;
                this.Invoke(cn, i, time);
            }
            else
            {
                label1.Text = time;
                progressBar1.Value = i;
            }
        }

        private void button2_Click(object sender, EventArgs e)
        {
            _thread = new Thread(DoWork);
            _thread.IsBackground = true;
            _thread.Start();
        }

        private void DoWork()
        {
            {
                int a = 1000;
                int b = 0;
                for (int i = 0; i < a; i++)
                {
                    for (int j = 0; j < a; j++)
                    {
                        for (int k = 0; k < a; k++)
                        {
                            b++;
                        }
                    }
                    if (_isDisposed)
                    {
                        return;
                    }
                    SetLabel(i, "Button2: " + DateTime.Now);
                }
            }
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            _isDisposed = true;
        }
  • 打赏
  • 举报
回复
private void SetLabel(int i, string time)
{
    if (this.InvokeRequired)
    {
        ChangeNumber cn = SetLabel;
        this.BeginInvoke(cn, i, time);
    }
    else
    {
        label1.Text = time;
        progressBar1.Value = i;
    }
}
anthony1119 2018-03-16
  • 打赏
  • 举报
回复
引用 19 楼 xuzuning 的回复:
问题在于你贴图的代码可以正确运行,并没有任何异常 该如何重现你的问题呢?
点击界面的按钮,在进度条未满的时候关闭窗体退出程序我说的异常就会出现了
xuzuning 2018-03-16
  • 打赏
  • 举报
回复
问题在于你贴图的代码可以正确运行,并没有任何异常 该如何重现你的问题呢?
anthony1119 2018-03-16
  • 打赏
  • 举报
回复
代码贴出来了,设置后台线程在进程关闭时并没有马上结束。增加了一个标志位,在FormClosing事件中修改,然后在委托中对标志位进行判断,依然会执行委托中调用界面控件的方法(为什么?)还希望能得到解答
xuzuning 2018-03-16
  • 打赏
  • 举报
回复
子线程应该是随主线程停止而停止的,显然,在我这里是的 你的为什么不是?想不通!
anthony1119 2018-03-16
  • 打赏
  • 举报
回复
引用 34 楼 xuzuning 的回复:
所以我应该退出讨论了,因为我无法模拟出你的错误场景(在我这里是不会出现异常的),仅靠猜测是不能推导出问题所在的 不过你可把否定式判断,改成肯定式判断,再试一下
好吧 点击button后马上关闭也不行么,好奇怪。 判断改了试了一下,还是一样的。
xuzuning 2018-03-16
  • 打赏
  • 举报
回复
所以我应该退出讨论了,因为我无法模拟出你的错误场景(在我这里是不会出现异常的),仅靠猜测是不能推导出问题所在的 不过你可把否定式判断,改成肯定式判断,再试一下
anthony1119 2018-03-16
  • 打赏
  • 举报
回复
引用 30 楼 sp1234 的回复:
按照我们设计和开发经验,异步处理流程就一定要使用异步方式来编写代码,不要将同步跟异步混淆起来,轻易不要阻塞(阻塞代码不及千分之一)。阻塞必然会产生一堆问题,这其实是一个设计理念问题而不是简单的编程语句问题。所以使用 BeginInvoke (我印象中)从不使用 Invoke。
@sp1234 你这里所说的异步的方式具体规范是指?或者有相关的链接吗?我找到的似乎相关性不是很大
anthony1119 2018-03-16
  • 打赏
  • 举报
回复
引用 29 楼 sp1234 的回复:
嗯,比如说同样是先执行了 Form.Close 代码,然后调用
this.Invoke(del_1);
和调用
this.BeginInvoke(del_1);
这两句,前一句出错,这个一般来说我们都作为设计上的一种缺陷来看待。因为 .net 类库设计者完全有能力使用跟后者相同的方法来直接结束,完全不用抛出异常,并不会产生什么问题。所以就觉得这里.net 功能就差这一点点改进功夫了。
受教了,看来我对异步的了解还不够
anthony1119 2018-03-16
  • 打赏
  • 举报
回复
引用 28 楼 xuzuning 的回复:
BeginInvoke 代替 Invoke 显然是在回避问题
出现异常(虽然我这里未出现)是要访问的目标不存在了(调用已释放的对象的异常
你只要在访问前判断一下是否已被释放了,如果已被释放,就不再去访问
直截了当,何必去绕弯子

@xuzuning 为什么判断是否已经被释放了还是会异常才是这个异常问题的最根本所在,我帖上图,应该能看的更清晰。
这张图第一次报现异常时截的,可以看到几个控件的IsDisposed状态都是false,却没有return。难道是因为访问控件太频繁,导致刚刚才完成了对控件释放状态的判断,控件就被释放了?
  • 打赏
  • 举报
回复
嗯,比如说同样是先执行了 Form.Close 代码,然后调用
this.Invoke(del_1);
和调用
this.BeginInvoke(del_1);
这两句,前一句出错,这个一般来说我们都作为设计上的一种缺陷来看待。因为 .net 类库设计者完全有能力使用跟后者相同的方法来直接结束,完全不用抛出异常,并不会产生什么问题。所以就觉得这里.net 功能就差这一点点改进功夫了。
  • 打赏
  • 举报
回复
按照我们设计和开发经验,异步处理流程就一定要使用异步方式来编写代码,不要将同步跟异步混淆起来,轻易不要阻塞(阻塞代码不及千分之一)。阻塞必然会产生一堆问题,这其实是一个设计理念问题而不是简单的编程语句问题。所以使用 BeginInvoke (我印象中)从不使用 Invoke。
xuzuning 2018-03-16
  • 打赏
  • 举报
回复
BeginInvoke 代替 Invoke 显然是在回避问题 出现异常(虽然我这里未出现)是要访问的目标不存在了(调用已释放的对象的异常) 你只要在访问前判断一下是否已被释放了,如果已被释放,就不再去访问 直截了当,何必去绕弯子
anthony1119 2018-03-16
  • 打赏
  • 举报
回复
引用 24 楼 xuzuning 的回复:
[quote=引用 20 楼 anthony1119 的回复:] 点击界面的按钮,在进度条未满的时候关闭窗体退出程序我说的异常就会出现了
这的不会报错! 即便是我在 button2_Click 方法中删去控件 Controls.Remove(progressBar1); Controls.Remove(label1); 也都不会报错的 [/quote] 移除的话应该并没有释放吧,依然可以访问所以不会出错。我理解你说的方法的意思,但是如#23说的那样,应该是Invoke这个方法跟Close之间的冲突,至于为什么运行过程中的异常在你那没有重现确实很奇怪,因为我这边是必现的。 使用BeginInvoke代替Invoke确实可以解决,去掉检查_isDisposed标志位这个步骤也没问题了。
anthony1119 2018-03-15
  • 打赏
  • 举报
回复
引用 16 楼 xuzuning 的回复:
又过了2天了,还在纠结这个不是问题的问题 当 Form1 关闭后,你还在执行 SetLabel(i, "Button2: " + DateTime.Now); 其中有 this.Invoke(cn, i, time); 或 label1.Text = time; 窗体都关闭了自然访问其中的控件是要报错的!
我说了呀,那我想要的不就是怎样去解决这个问题吗? 我想要的是@以专业开发人员为伍 那样的从表面上去解决以及从根本上解决的方案,代码贴出来了我也说了,即使是在formclosing事件中修改标识位并从方法中return出来,任然有相同的问题。在SetLabel方法中增加
if(_IsDesposed) return;
也不行
加载更多回复(16)

110,536

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧