zoukankan      html  css  js  c++  java
  • [C#参考]UI和线程(一)

    Windows是一个多任务的系统,如果你使用的是windows 2000及其以上版本,你可以通过任务管理器查看当前系统运行的程序和进程。

    什么是进程呢?当一个程序开始运行时,它就是一个进程,进程所指包括运行中的程序和程序所使用到的内存和系统资源。而一个进程又是由多个线程所组成的,线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数。多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。

    一 关于Thread的说明

    在.net framework class library中,所有与多线程机制应用相关的类都是放在System.Threading命名空间中的。其中提供Thread类用于创建线程,ThreadPool类用于管理线程池等等,此外还提供解决了线程执行安排,死锁,线程间通讯等实际问题的机制。如果你想在你的应用程序中使用多线程,就必须包含这个类。Thread类有几个至关重要的方法,描述如下:

    • Start():启动线程 
    • Sleep(int):静态方法,暂停当前线程指定的毫秒数 
    • Abort():通常使用该方法来终止一个线程 
    • Suspend():该方法并不终止未完成的线程,它仅仅挂起线程,以后还可恢复。 
    • Resume():恢复被Suspend()方法挂起的线程的执行

    线程入口使程序知道该让这个线程干什么事在C#中,线程入口是通过ThreadStart代理(delegate)来提供的你可以把ThreadStart理解为一个函数指针,指向线程要执行的函数当调用 Thread.Start()方法后,线程就开始执行ThreadStart所代表或者说指向的函数。 ThreadState在各种情况下的可能取值如下:

    • Aborted:线程已停止 
    • AbortRequested:线程的Thread.Abort()方法已被调用,但是线程还未停止 
    • Background:线程在后台执行,与属性Thread.IsBackground有关 
    • Running:线程正在正常运行 
    • Stopped:线程已经被停止 
    • StopRequested:线程正在被要求停止 
    • Suspended:线程已经被挂起(此状态下,可以通过调用Resume()方法重新运行) 
    • SuspendRequested:线程正在要求被挂起,但是未来得及响应 
    • Unstarted:未调用Thread.Start()开始线程的运行 
    • WaitSleepJoin:线程因为调用了Wait(),Sleep()或Join()等方法处于封锁状态

    二 Winform中使用的thread

    首先可以看看最直接的方法,也是.net 1.0下支持的方法。但请注意的是,此方法在.net 2.0以后就已经是一种错误的方法了。

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        private void Form1_Load(object sender, EventArgs e)
        {
            Thread thread = new Thread(ThreadFuntion);
            thread.IsBackground = true;
            thread.Start();
        }
        private void ThreadFuntion()
        {
            while (true)
            {
                this.textBox1.Text = DateTime.Now.ToString();
                Thread.Sleep(1000);
            }
        }
    }

    这段code在vs2005或者2008上都抛出异常 :Cross-thread operation not valid:Control 'textBox1' accessed from a thread other than the thread it was created on . 这是因为.net 2.0以后加强了安全机制,不允许在winform中直接跨线程访问控件的属性。那么怎么解决这个问题呢,下面提供几种方案。

    第一种方案: 在Thread创建之前,将Control.CheckForIllegalCrossThreadCalls 设为 false。 此代码告诉编译器:在这个类中我们不检查跨线程的调用是否合法(如果没有加这句话运行也没有异常,那么说明系统以及默认的采用了不检查的方式)。然而,这种方法不可取。我们查看CheckForIllegalCrossThreadCalls 这个属性的定义,就会发现它是一个static的,也就是说无论我们在项目的什么地方修改了这个值,他就会在全局起作用。而且像这种跨线程访问是否存在异常,我们通常都会去检查。如果项目中其他人修改了这个属性,那么我们的方案就失败了,我们要采取另外的方案。

    第二种方案:

    namespace WindowsFormsApplication1
    {
        public partial class Form1 : Form
        {
            //子线程计数器
            private int counter; 
    
            public Form1()
            {
                InitializeComponent();
            }//Form1
    
            private void button1_Click(object sender, EventArgs e)
            {
                 Thread thread = new Thread(new ThreadStart(StartWorkFromUIThread));
                 //设置线程为后台线程,这样的话前台UI线程死亡时它也跟着死亡
                  //如果设置为前台线程,UI线程死亡后这个线程是不会死亡的,所以即使关闭了窗口,程序也不会结束。因为有前台线程在运行
                  thread.IsBackground = true;
                thread.Start();
    
            }//button1_Click
    
            //子线程执行序
            private void StartWorkFromUIThread()
            {
                while(true)
                {
                    if(this.InvokeRequired)
                    {
                      //Invoke的作用就是让后面的函数到主线程中去执行
                        //所以在Invoke里面的函数中,不能有Sleep,这样会卡死界面
                        BeginInvoke(new EventHandler(RunsOnWorkerThread), null);
                    }
                    else
                    {
                        RunsOnWorkerThread(this,null);
                    }
                    Thread.Sleep(500);
                }
                
            }//StartWorkFromUIThread
    
            //要在主线程中做相应的工作
            private void RunsOnWorkerThread(object sender, EventArgs e)
            {
                //计数器计算调用的次数
                  counter++;
                lblTimeShow.Text = DateTime.Now.ToString()+ " " + counter.ToString();
            }//RunsOnWorkerThread
    
        }//class
    }

    上面代码知识点的解释:

    InvokeRequired,获取一个值,该值指示调用方在对控件进行方法调用时是否必须调用 Invoke 方法,因为调用方位于创建控件所在的线程以外的线程中。上面的this就是拥有控件的窗体,这句话就是测试一下当前的位置是不是在UI线程中。不是的话返回true,是的话返回false。

    如果控件的 Handle 是在与调用线程不同的线程上创建的(说明您必须通过 Invoke 方法对控件进行调用),则为 true;否则为 false。

    如果不需要 Invoke(调用发生在同一线程上),或者如果控件是在另一个线程上创建的但尚未创建控件的句柄,则 InvokeRequired 可以返回 false。


    总结:

    在Winform多线程中,由于子线程是不能直接操作UI线程中的控件的,所以利用Invoke(函数,参数),让子线程能调用一个函数完成对控件的操作,当然这个函数也就会在UI线程中运行,这个函数中不能有过多的计算和sleep的出现,否则会阻塞UI线程,界面卡死。

    Invoke是很好理解的,UI线程(主线程)可以new一个新的子线程,然后告诉它去执行哪个函数;同样的,子线程可以起让UI主线程去执行一个函数。

    通过上叙代码,可以看到问题已经被解决了,通过等待异步,我们就不会总是持有主线程的控制,这样就可以在不发生跨线程调用异常的情况下完成多线程对winform多线程控件的控制了。

  • 相关阅读:
    part11-1 Python图形界面编程(Python GUI库介绍、Tkinter 组件介绍、布局管理器、事件处理)
    part10-3 Python常见模块(正则表达式)
    Cyclic Nacklace HDU
    模拟题 Right turn SCU
    状态DP Doing Homework HDU
    Dp Milking Time POJ
    区间DP Treats for the Cows POJ
    DP Help Jimmy POJ
    Dales and Hills Gym
    Kids and Prizes Gym
  • 原文地址:https://www.cnblogs.com/stemon/p/4208327.html
Copyright © 2011-2022 走看看