您的位置:首页 - 教程 - WinForm - 正文
winform开发总结使用线程的必要性及正确的使用方式

winform程序中使用线程的必要性:

单线程操作在执行耗时任务时会造成界面假死,带来非常差劲的用户体验,有时候甚至会影响到正常的业务执行,使用多线程做相关操作实属不得已之举。

那么在编写程序之前必须要明白的一个点就是窗体的UI的操作只能通过UI线程来执行,其他线程如果要去执行窗体中的控件值修改或者其它【任何和窗体线程相关的操作】,就会报异常,所有人都知道的。为了适应这一特性,于是就有了这样的写法:

  private void button1_Click(object sender, EventArgs e)
        {
            this.BeginInvoke(new Action(delegate()
            {
                this.button1.Text = "test";
            }));
        }

意思很明显就是在本窗体中执行如下代码,说白了就是让括号中的代码在UI线程中执行,如果只是执行一个很简单的任务不会有任何问题,因为时间够快,给人的感觉好像窗体并没有因为这样写就假死。现在把代码改成如下这样:

  private void button1_Click(object sender, EventArgs e)
        {
            this.BeginInvoke(new Action(delegate()
            {
                for (int i = 0; i < 100; i++)
                {
                    this.button1.Text = i.ToString();
                    Thread.Sleep(1000);
                }
            }));
        }

预期的执行结果应该是,每隔一秒按钮上边的文本就会自加1直到100,但结果并不是这样,当点击按钮之后,窗体会进入假死状态,点击不会有任何响应。这篇文章就是要解决这样的问题。主要也是做一个简单的总结,备用。

要处理这样的问题最简单粗暴的方式是这样直接忽略掉其他线程不可以执行UI。代码非常简单,只需要在界面初始化中添加如下代码就可以,

 public Form1()
        {
            InitializeComponent();
            CheckForIllegalCrossThreadCalls = false;//时候捕获对错误线程的调用...  忽略掉自然就可以在其他线程中去访问窗体线程了。
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Thread t = new Thread(() =>
            {
                this.button1.Text = "测试";
            });
            t.Start();
        }

这种方式确实已经做到可以在不同线程中去操作窗体线程,但并没有什么卵用,遇到上边的那种情况每隔一秒让按钮的数字自增一,依然无法做到。

 

请回头看标颜色的那句话。

然后再看看Invoke,BeginInvoke到底是什么东西:

直接F12找到签名对应的解释

//
// 摘要:
// 在创建控件的基础句柄所在线程上异步执行指定委托。
//
// 参数:
// method:
// 对不带参数的方法的委托。
//
// 返回结果:
// 一个表示 System.Windows.Forms.Control.BeginInvoke(System.Delegate) 操作的结果的 System.IAsyncResult。
//
// 异常:
// System.InvalidOperationException:
// 找不到适当的窗口句柄。
[EditorBrowsable(EditorBrowsableState.Advanced)]
public IAsyncResult BeginInvoke(Delegate method);
 //
        // 摘要:
        //     在拥有此控件的基础窗口句柄的线程上执行指定的委托。
        //
        // 参数:
        //   method:
        //     包含要在控件的线程上下文中调用的方法的委托。
        //
        // 返回结果:
        //     正在被调用的委托的返回值,或者如果委托没有返回值,则为 null。
        public object Invoke(Delegate method);

 

 

关键字: 拥有此控件的基础窗口句柄的线程上执行执行的委托。同步异步的区别

 

 this.BeginInvoke(new Action(delegate()

            {
                for (int i = 0; i < 100; i++)
                {
                    this.button1.Text = i.ToString();
                    Thread.Sleep(1000);
                }
            }));

那么在这里的意思就是在窗体线程中执行button.text=i.tostring,然后让窗体线程休眠1000毫秒,窗体休眠了,自然而然就不会对你的操作做出响应,不管是不是异步都是在窗体线程中执行的,显而易见问题是出在这里的,那么既然知道了问题所在。解决的办法也非常简单,那就是,

    让所有和窗体操作无关的任务不要在窗体线程中执行,所有和窗体相关操作的动作全部放到窗体线程中去执行,大家各行其道,问题就自然解决了。刚刚的按钮文本每秒加1,就可以用下边的这种方式来写:

 private void button1_Click(object sender, EventArgs e)
        {
            Thread t = new Thread(() =>
            {
                for (int i = 0; i < 100; i++)
                {
                    Thread.Sleep(1000);
                    this.button1.Invoke(new Action(delegate()
                    {
                        this.button1.Text = i.ToString();
                    }));
                } 
            });
            t.Start();
        }

没错,就是这样,新开一个线程,让所有的操作都在线程中执行,其中如果有涉及到对窗体的操作转会到窗体线程执行对应操作,或者像这样

 public Form1()
        {
            InitializeComponent();
            CheckForIllegalCrossThreadCalls = false;//忽略其他线程执行UI的错误
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Thread t = new Thread(() =>
            {
                for (int i = 0; i < 100; i++)
                {
                    Thread.Sleep(1000);
                    this.button1.Text = i.ToString();
                }
            });
            t.Start();
        }

这种方式明显是有点取巧,而且在一定情况下会造成窗体闪烁,可能会不稳定,比如多个线程同时执行一个按钮的text显示,但至少这种方式写起来没那么麻烦。至于如何取舍就具体问题具体分析处理了。

 

评论: