12
2018
05

.Net WinForm中开启多线程方式和多线程中操作控件方法

一、背景

    最近用.Net WinForm做一个桌面应用程序,刚开始有一些问题,后面总结出做WinForm最常见的两个问题,就是多线程的使用和多线程中的控件操作。

其实挺简单,但是没做过的话也会耽误一点时间。


二、技术

1、使用多线程

为什么要开多线程?因为不开多线程,界面会出现卡死的情况,体验很不好。

先看一下线程要做的事情(for循环内部代码):

                        Action action = () =>
                        {
                            while (true)
                            {
                                try
                                {
                                    var stop = false;
                                    lock (lockObj_stop)
                                    {
                                        stop = IsStop;
                                    }
                                    if (stop)
                                    {
                                        OperationLog.AddLog(new OperationLogModel()
                                        {
                                            CreateTime = DateTime.Now,
                                            Remark = $"线程:{Thread.CurrentThread.ManagedThreadId},用户主动停止!线程任务结束!"
                                        });
                                        break;//用户主动停止,本线程结束
                                    }
                                    var otherSuccess = false;
                                    lock (lockObj_success)
                                    {
                                        otherSuccess = IsSuccess;//AddLog里有lock操作,lock嵌套容易死锁,所以找个临时中间变量
                                    }
                                    if (otherSuccess)
                                    {
                                        OperationLog.AddLog(new OperationLogModel()
                                        {
                                            CreateTime = DateTime.Now,
                                            Remark = $"线程:{Thread.CurrentThread.ManagedThreadId},其他线程预约成功!线程任务结束!"
                                        });
                                        break;//其他线程成功预约到,本线程结束
                                    }
                                    var ret = requestOne(model);//请求网络,model是具体数据,for循环列表创建线程,每次model数据不同
                                    if (ret)
                                    {
                                        lock (lockObj_success)
                                        {
                                            IsSuccess = true;
                                        }
                                        OperationLog.AddLog(new OperationLogModel()
                                        {
                                            CreateTime = DateTime.Now,
                                            Remark = $"线程:{Thread.CurrentThread.ManagedThreadId},预约成功!线程任务结束!"
                                        });
                                        break;//结束
                                    }
                                }
                                catch (Exception ex)
                                {
                                    Log4netHelper.Error("出错!", ex);
                                }
                            }
                        }

怎么开多线程?

方式1:线程池线程

(线程池把线程加入队列,不一定会立即执行,可能要排队)

                        var task = System.Threading.Tasks.Task.Factory.StartNew(action);


方式2:直接开新线程

(立即执行):

                        var thread = CommonFun.StartNewThread(action);

CommonFun.StartNewThread方法:

        /// <summary>
        /// 开启新线程并返回线程对象
        /// </summary>
        /// <param name="action"></param>
        /// <returns></returns>
        public static Thread StartNewThread(Action action)
        {
            Thread thread = new Thread(delegate () { action(); });
            thread.IsBackground = true;
            thread.Start();
            return thread;
        }

上面是两种开启多线程的方法,自己封装的方法StartNewThread跟系统提供的方法参数一样,可以方便切换,如果要响应快,电脑资源充足的话就用方式2,如果可以慢点、线程池满了允许慢慢排队执行则可以使用方式1。


2、多线程中操作控件问题

一般情况下,多线程中直接操作控件(比如:把一个按钮变成Enable状态、给textbox赋值),程序一运行就会报错,报错信息如下:


解决方法1:设置不检查跨线程调用属性

如果只是操作极少量的控件,这里有一个方法,在Form构造函数中,加入以下语句就不会报错,但要自己保证代码中不会有多个线程同时调用它,是一种非线程安全的操作,不推荐使用:

属性说明:


解决方法2:调用控件Invoke委托

调用控件的Invoke方法(同步)或BeginInvoke方法操作控件,这种方法是线程安全的,推荐使用

定义专用委托示例:

        //建立个委托
        private delegate void CanbookcountAppendTextDelegate(string strshow);
        /// <summary>
        /// RichTextBox追加文字,并自动滚动到最下方
        /// </summary>
        /// <param name="texttoappend"></param>
        public void CanbookcountAppendText(string texttoappend)
        {
            if (this.txt_canbookcount.InvokeRequired)
            {
                this.txt_canbookcount.Invoke(new CanbookcountAppendTextDelegate(CanbookcountAppendText), texttoappend);
            }
            else
            {
                this.txt_canbookcount.AppendText(texttoappend);
                txt_canbookcount.SelectionStart = txt_canbookcount.Text.Length;
                txt_canbookcount.ScrollToCaret();//滑动到最下方
            }
        }

使用匿名委托示例:

        /// <summary>
        /// 查询按钮设置为有效或失效
        /// </summary>
        /// <param name="enabled"></param>
        public void QuerycanbookcountBtnEnabled(bool enabled)
        {
            if (btn_querycanbookcount.InvokeRequired)
            {
                this.btn_querycanbookcount.Invoke(new MethodInvoker(delegate ()
                {
                    QuerycanbookcountBtnEnabled(enabled);
                }));
            }
            else
            {
                btn_querycanbookcount.Enabled = enabled;
            }
        }


上面这两个示例调用方法可以在主线程和其他线程中被调用,里面有判断 Control.InvokeRequired属性,看下面说明,

在窗口线程中,InvokeRequired值为false,会直接调用赋值语句:

btn_querycanbookcount.Enabled = enabled;

而在多线程中,InvokeRequired值为true,会调用控件的Invoke方法。

这里,比较难理解的东西重点在于QuerycanbookcountBtnEnabled内部的Invoke方法的委托还是方法本身 QuerycanbookcountBtnEnabled(enabled),不会造成死循环?

当然不会,Invoke方法的作用,就是把整个方法委托给窗口线程(主线程)去处理,调用这一步它自己不执行委托方法里面的逻辑,然而,委托方法交给窗口线程(主线程)后,窗口线程(主线程)执行时就会判断InvokeRequired值为false,执行:btn_querycanbookcount.Enabled = enabled,然后就完事了。


上面只是封装了一下方法,使得这个控件操作方法可以在所有线程内被调用。

当然了,如果明确知道自己在主线程以外的线程调用控件,也可以直接使用Invoke方法,不用那么多判断,例如:

                btn.Invoke(new MethodInvoker(delegate ()
                {
                    btn.Enabled = true;
                }));


好了,这两个常见问题的处理方法就是以上内容了~~~


最后,贴一下做的程序界面:

题外话:之前开了200多个线程去请求网络,放在本地电脑执行,CPU只占2%~3%,放到单核的某云服务器上运行时cpu就到了99%,CPU资源也算是“充分利用“了,好在还没卡死,还可以关掉程序~笑哭~



版权声明:
作者:真爱无限 出处:http://www.pukuimin.top 本文为博主原创文章版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接.
« 上一篇下一篇 »

相关文章:

评论列表:

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。