警告:winForm跨线程调用UI控件使用Invoke可能造成线程卡死的特殊情况,请使用BeginInvoke异步调用

最近在做一个简单点的winform项目的时候,因为要在子线程中控制主线程UI显示日志,在停止线程的时候竟然出现所有线程卡死的情况,这下疯了,难道我做游戏久了,竟然连简单的winform跨线程都不会了么?请看代码,大家看看能否找出原因

using System;
using System.Threading;
using System.Windows.Forms;

namespace GamePlugInDemo
{
    public partial class Form2 : Form
    {
        /// <summary>
        /// 输出事件
        /// </summary>
        Action<string> outputAction;
        /// <summary>
        /// 工作线程是否工作标记位
        /// </summary>
        bool bRun=false;
        /// <summary>
        /// 工作线程
        /// </summary>
        Thread workThread;
        public Form2()
        {
            InitializeComponent();
        }
        protected override void OnLoad(EventArgs e)
        {
            this.button1.Text = "开始";
            outputAction += Output;
            base.OnLoad(e);
        }
        private void Output(string info)
        {
            if (this.InvokeRequired)//这个请求是跨线程的话,那么就使用invoke调用控件
            {
                this.Invoke(outputAction, info);
            }
            else
            {
                //不是跨线程操作,直接使用控件
                this.label1.Text = info;
            }
        }
        private void button1_Click(object sender, EventArgs e)
        {
            if (!bRun)
            {//没运行时点击按钮-开始子线程工作
                bRun = true;
                workThread = new Thread(Work);
                workThread.Start();
                this.button1.Text = "停止";
            }
            else
            {//子线程开始了后点击按钮-停止子线程工作

                bRun = false;
                workThread.Join();//等待子线程停止
                this.button1.Text = "开始";
                Console.WriteLine("停止子线程按钮完毕");

            }
        }

        private void Work()
        {
            while (bRun)
            {
                Output(DateTime.Now.ToString("yyyy-MM-dd-HH:mm:ss"));
                Thread.Sleep(100);
            }
            Output("结束时间为:"+DateTime.Now.ToString("YYYY-MM-dd-HH:mm:ss"));
            Console.WriteLine("子线程结束");
        }
    }
}

以上代码就是一个简单的按钮和一个label显示当前时间,每100毫秒刷新一次UI的时间显示,点击按钮会开始和停止.

开始阶段没有问题,但是在点击按钮停止线程的时候需要确定工作线程已经结束后才继续响应UI(正式项目其实是为了暂停UI响应,保证工作线程的计算结果正确,工作线程正常退出!);此时就出现了整个程序卡死的情况,我百撕不得骑姐,检查了下代码,发现跨线程调控件没有问题,发现工作线程Output也运行了,可最后的Console却一次没有运行,打断点发现工作线程却一直没有结束.所以按钮哪里一直在等待workThread.Join();卡主,然后工作线程不能结束,主线程一直等待!于是就出现了卡死!貌似永远不能结束了!

不知道同学们有没有发现上面的原因在哪里呢?

经过几个小时的断点检查,竟然发现是跨线程输出这里卡住了??怎么回事?用invoke调用主线程UI没错啊,大家也都这样用的?然后我仔细看了下invoke的解释:在拥有控件的基础窗口句柄的线程上,用指定的参数列表执行指定委托。

我去?原来是换到主线程调用而已,可主线程在干啥呢?主线程不是在等待子线程结束吗?所以主线程一直没有运行这个invoke内容,而没有给子线程返回,所以子线程在等待主线程调用invoke结束的返回,好吧,主线程等子线程才继续工作,子线程等主线程工作完了才正式结束,于是一个双线程死锁就出现了!

好吧好吧,看起来只要让某一个线程别等待另外一个线程就可以解开这个锁了.但是业务需要必须等待子线程正常结束,看来只能让子线程别等主线程的返回就可以了!所以我就用了BeginInvoke函数来调用子线程了!这个函数不用等待返回会继续子线程的工作!于是锁就解开了!以前写winform总是习惯用invoke,毕竟字打的少点,效果也是杠杠的.经过这次,发现同步调用对线程的影响还是很大的!SO:以后的跨线程调用就用                this.BeginInvoke(outputAction,info)函数!

以上的代码改掉其中一个就可以了!代码如下:

 private void Output(string info)
        {
            if (this.InvokeRequired)//这个请求是跨线程的话,那么就使用invoke调用控件
            {
                //this.Invoke(outputAction, info);//警告,不要用同步调用,不然会让你哭
                this.BeginInvoke(outputAction, info);
            }
            else
            {
                //不是跨线程操作,直接使用控件
                this.label1.Text = info;
            }
        }

OK!解决了一个疑难BUG.哈哈!心里舒服了,去找公司新来的主播聊骚去!
 

异步委托跨线调用控件是指在多线程情况下,通过使用异步委托来实现对控件跨线调用。这种方法可以解决在主线程中访问控件可能出现的界面卡死、无响应等问题。 在C#中,可以通过使用Invoke或BeginInvoke方法来实现异步委托跨线调用控件使用Invoke方法可以确保任务在主线程上执行,而BeginInvoke方法允许任务在后台线程上执行。具体的使用方法如下: 1. 在主线程中创建委托方法,该方法用于更新控件的属性或执行其他与控件相关的操作。 2. 当需要在后台线程中更新控件时,使用Invoke或BeginInvoke方法,将委托方法作为参数传递给它们。 3. 在委托方法中,通过控件Invoke方法或BeginInvoke方法来更新控件注意,为了避免线程冲突和不可预料的结果,强烈不推荐使用Control.CheckForIllegalCrossThreadCalls = false;这种方法。相反,应该采用合理的方法,即在主线程中负责UI界面的度,将耗时的操作放在后台线程中进行,并使用Invoke委托来触发UI刷新。这样可以确保界面的稳定性和响应性,避免出现卡死、无响应或报错等问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [C#跨线调用控件](https://blog.csdn.net/tonghaojie/article/details/78767046)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [C# winform delegate委托以及异步调用委托(跨线调用控件UI)使用总结](https://blog.csdn.net/gxlzhhx/article/details/128568462)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值