• 委托与线程的见解(下)——线程


    线程相关

            关于线程的概念很多,简单的说,线程是程序执行流的最小单元,如果把进程比作一条河流,那么线程就是河流的一条小支流。他是独立执行,却可能对主进程有影响。

    常识

    1. 前台线程和后台线程:通过Thread类新建线程 thread1 默认为前台线程。当所有前台线程关闭时,所有的后台线程也会被直接终止,不会抛出异常。将前台线程转后台线程,只需thread1.IsBackground = true
    2. 挂起(Suspend)和唤醒(Resume):由于线程的执行顺序和程序的执行情况不可预知,所以使用挂起和唤醒容易发生死锁的情况,在实际应用中应该尽量少用。
    3. 阻塞线程:Join,阻塞调用线程,直到该线程终止。
    4. 终止线程:Abort:抛出 ThreadAbortException 异常让线程终止,终止后的线程不可唤醒。Interrupt:抛出 ThreadInterruptException 异常让线程终止,通过捕获异常可以继续执行。
    5. 线程优先级:AboveNormal BelowNormal Highest Lowest Normal,默认为Normal。

    单线程与多线程 

            单线程就是,任务一个一个地做,必须做完一个任务后,再去做另一个任务。多线程就是一会做这个任务,一会做那个任务,每个任务做一会,不停的切换。显然,最后把所有的任务做完,多线程必定比单线程更耗费时间。为什么?因为,多线程要在不同的任务之间切换,切换肯定是要耗费时间的。那么问题来了,既然多线程比单线程更耗费时间,为什么还要多线程? 单线程有一个致命的问题,就是一个线程运行的整个过程中,其他线程必须等待,不能响应用户的命令,用户体验太差,好像电脑死机一样。

    线程池:线程池线程默认为后台线程(IsBackground)
     

    由于线程的创建和销毁需要耗费一定的开销,过多的使用线程会造成内存资源的浪费,出于对性能的考虑,于是引入了线程池的概念。线程池维护一个请求队列,线程池的代码从队列提取任务,然后委派给线程池的一个线程执行,线程执行完不会被立即销毁,这样既可以在后台执行任务,又可以减少线程创建和销毁所带来的开销。

    同步与异步

             同步的使用场景:多个线程同时访问一块数据,也叫共享区。对于多个线程同时访问一块数据的时候,必须使用同步,否则可能会出现不安全的情况。比如数据库中的脏读。但是,多个线程同时访问一块数据,有一种情况不需要同步技术,那就是原子操作,也就是说操作系统在底层保证了操作要么全部做完,要么不做。

            异步的使用场景:只有一个线程访问当前的数据。比如,观察者模式,没有共享区,主题发生变化,通知观察者更新,主题继续做自己的事情,不需要等待观察者更新完成后再工作。

    创建线程实例

    创建一个最简单的单线程:


     
    //实例化线程
    new System.Threading.Thread(Function).Start();

    private void Function()
    {
    MessageBox.Show("线程");
    }
    这种线程调用方式,仅适用于无参方法调用,同时,可以限制线程使用的堆栈大小,只需在Thread(Function,int)增加字节为单位的整形参数。同时,这种方式,不需要去关闭线程,垃圾回收机制会将其自动回收,可好比这就是一个参数对象。

    实现同样效果还可以这样创建线程:

    Thread thread1 = new Thread(Function);
    thread1.Start();
    但是这个需要注意的是,一般在线程不用时,需要用线程的Abort()方法结束线程。

    此外,还可以这样写

    //利用委托
    Thread thread2 = new Thread(delegate () { MessageBox.Show("线程委托"); });
    thread2.Start();

    //Lambda表达式的形式
    Thread thread3 = new Thread(() => { MessageBox.Show("Lambda表达式"); });
    thread3.Start();
    本着学习的态度来找知识的话,现在应该会提出,如果要传参数,那应该怎么写呢,下面就说一下创建有参数的线程方法。

    线程传参

    首先,最简单的,就是利用线程的公共API传参(也就是Start()方法)

    /// <summary>
    /// 调用方法
    /// </summary>
    /// <param name="e">参数对象</param>
    private void Function(object e)
    {
    MessageBox.Show(e.ToString());
    }

    //调用
    Thread thread = new Thread(Function);
    thread.Start("线程传参");
    这种传参方式有时可能不太实用(参数只有一个),那么我们还可以自己定义一个类对象,来实现传参

    public class ThreadEntry
    {
    private int intPara;
    private string strPara;
    public ThreadEntry(int intPara, string strPara)
    {
    this.intPara = intPara;
    this.strPara = strPara;
    }

    /// <summary>
    /// 调用方法
    /// </summary>
    public void Method()
    {
    MessageBox.Show(string.Format(strPara, intPara));
    }
    }

    //调用
    Thread thread = new Thread(new ThreadEntry(1,"第{0}种类传参").Method);
    thread.Start();
    除此之外,还可以提前定义一个线程的抽象类,来实现参数传递方法,这样调用起来更方便。尽管两种方法的可拓展性都挺高的,但是用抽象类的办法,还是要好一点,因为在实例化对象时,会少一个层级。

    首先,我们需要定义一个自己的线程抽象类

    abstract class MyThread
    {
    Thread thread = null;

    abstract public void run();

    public void start()
    {
    if (thread == null)
    thread = new Thread(run);
    thread.Start();
    }
    }
    之后再定义一个它的子类

    class ParaThread : MyThread
    {
    private int intPara;
    private string strPara;
    public ParaThread(int intPara, string strPara)
    {
    this.intPara = intPara;
    this.strPara = strPara;
    }

    override public void run()
    {
    MessageBox.Show(string.Format(strPara, intPara));
    }
    }
    这里只用了两个参数的构造函数,所以只能传两个参数,如果说你想传更多的参数,只需要对构造函数重写就行了,之前的对象类,需要更多的传参,也是重写就行了。

    核心东西写完后,简单调用一下就好了

    ParaThread thread = new ParaThread(2, "第{0}种类传参");
    thread.start();
    线程传参方式很多,这里只是对常用的进行了举例。

    异步线程

     对于异步线程,前面已做过简要的讲解,这里便举一个实例,供参考学习。后面还有一个task任务也是属于异步线程的范畴。

    private Control _control;
    private Thread beginInvokeThread;
    public delegate void beginInvokeDelegate();
    public event beginInvokeDelegate Run;//委托事件

    /// <summary>
    /// 执行事件
    /// </summary>
    /// <returns>返回执行结果:true</returns>
    private bool Do()
    {
    if (this.Run != null)
    RaiseEvent(Run);
    return true;
    }

    /// <summary>
    /// 事件处理函数
    /// </summary>
    /// <param name="handler">处理</param>
    private void RaiseEvent(beginInvokeDelegate handler)
    {
    if (handler != null)
    {
    beginInvokeDelegate beginInvoke = handler;
    beginInvoke();
    }
    }

    /// <summary>
    /// 异步线程入口
    /// </summary>
    /// <param name="control">作用控件</param>
    /// <param name="run">执行函数</param>
    public void DoBeginInvoke(Control control, beginInvokeDelegate run)
    {
    Run = run;
    _control = control;
    //开启异步线程
    System.Threading.ThreadStart s = new System.Threading.ThreadStart(new System.Threading.ThreadStart(Result));
    beginInvokeThread = new System.Threading.Thread(s);
    beginInvokeThread.Name = "异步线程";
    beginInvokeThread.Start();
    }

    /// <summary>
    /// 中断执行
    /// </summary>
    private void EndProcess()
    {
    if (beginInvokeThread.IsAlive) beginInvokeThread.Abort();//结束线程
    }

    /// <summary>
    /// 执行结果处理
    /// </summary>
    private void Result()
    {
    bool ok = Do();
    _control.BeginInvoke(new System.Threading.ThreadStart(delegate()
    {
    if (ok)
    {
    EndProcess();
    }
    }));
    }
    这是一个异步线程类,结构比较清晰,很容易就能明白,只要知道了其中的原理,其实可以写的更加简化。这里需要注意的是,这是一个UI异步线程,如果在异步线程还未执行完成时,强制关掉了窗体,可能会触发异常,所以关闭窗体时调用EndProcess()方法,也可尝试将线处理为后台线程,即IsBackground 设为true。

    同步线程

    同步与异步在程序上的差别不是很大,基本上就是将上面的异步线程实例中的BeginInvoke更换成Invoke便可以实现同步线程的效果。

    多线程

    线程池[ThreadPool]

    这是一种相对较简单的方法,适应于一些需要多个线程而又较短任务(如一些常处于阻塞状态的线程) ,明显缺点就是对创建的线程不能加以控制及设置其优先级。由于每个进程只有一个线程池,所以ThreadPool类的成员函数都为static。

    核心函数介绍:

    //调用成功则返回true,它的另一个重载函数类似,只是委托不带参数而已
    public static bool QueueUserWorkItem(
        WaitCallback callBack,//要创建的线程调用的委托
    object state //传递给委托的参数
    );
    public static RegisteredWaitHandle RegisterWaitForSingleObject(
    WaitHandle waitObject,// 要注册的 WaitHandle
    WaitOrTimerCallback callBack,// 线程调用的委托
    object state,//传递给委托的参数
    int TimeOut,//超时,单位为毫秒,
    bool executeOnlyOnce file://是否只执行一次
    );
    public delegate void WaitOrTimerCallback(
    object state,//也即传递给委托的参数
    bool timedOut//true表示由于超时调用,反之则因为waitObject
    );
    使用实例:

    首先定义一个多线程操作类

    public class Multithreading
    {
    public static int iCount = 0;
    public static int iMaxCount = 0;
    public ManualResetEvent rEvent;

    public Multithreading(int iMaxCount, ManualResetEvent rEvent)
    {
    this.iMaxCount = count;
    this.rEvent = rEvent;
    }

    public void DoProcess(object i)
    {
    Console.WriteLine("Thread操作[" + i.ToString() + "]");
    Thread.Sleep(1000);
    //Interlocked.Increment()操作是一个原子操作,作用是:iCount++ 具体请看下面说明
    //原子操作,就是不能被更高等级中断抢夺优先的操作。你既然提这个问题,我就说深一点。
    //由于操作系统大部分时间处于开中断状态,
    //所以,一个程序在执行的时候可能被优先级更高的线程中断。
    //而有些操作是不能被中断的,不然会出现无法还原的后果,这时候,这些操作就需要原子操作。
    //就是不能被中断的操作。
    Interlocked.Increment(ref iCount);
    if (iCount == iMaxCount)
    {
    Console.WriteLine("发出结束信号!");
    //将事件状态设置为终止状态,允许一个或多个等待线程继续。
    eventX.Set();
    }
    }
    }
    有了这个操作类,我们就可以将想要传的参数通过操作类的构造函数传递过去,在方法中使用。有了操作类,接下来就可以创建线程池,实现简单的多线程处理效果了。

    //新建ManualResetEvent对象并且初始化为无信号状态
    ManualResetEvent rEvent = new ManualResetEvent(false);
    ThreadPool.SetMaxThreads(3, 3);
    int executeCount = 10;
    Multithreading t = new Multithreading(executeCount, rEvent);
    for (int i = 0; i < executeCount; i++)
    {
    ThreadPool.QueueUserWorkItem(new WaitCallback(t.DoProcess), i);
    }

    //WaitOne 阻止当前线程,直到当前 WaitHandle 收到信号为止。
    rEvent.WaitOne(Timeout.Infinite, true);
    task

    相对于线程池,task的可控制性就更高了,值得注意的是,task线程是异步执行的,也就是说task任务从作用上说是异步线程,从模式上说是属于多线程。这里只对task的用法做一个简单的介绍,想要深入理解,可以去百度一下专门讲解task的文章。

    调用方法

    private static void StartTask(object e)
    {
    Console.WriteLine("执行Task任务【{0}】", e);
    Thread.Sleep(1000);
    }
    创建task任务

    Console.WriteLine("创建Task任务");
    new Task(StartCode, 1).Start();
    Console.WriteLine("创建Task任务完成");
    Thread.Sleep(1000);
    task任务可以用Cancel()方法取消,但是这是个异步请求,Task可能已经完成了。

    再举一个计算的实例

    简单的计算方法

    private static Int Sum(Int i)
    {
    Int sum = 0;
    for (; i > 0; i--)
    checked { sum += i; }
    return sum;
    }
    task任务创建与使用

    Task<Int> t = new Task<Int>(i => Sum((Int)i), 10000000);
    t.Start();
    //Wait显式的等待一个线程完成
    t.Wait();

    Console.WriteLine("计算结果:" + t.Result);
    结果输出还可以这么写

    Task cwt = t.ContinueWith(task=>Console.WriteLine("计算结果:{0}",task.Result));
    cwt.Wait();
    task创建调用的其他写法

    //using task factory
    TaskFactory tf = new TaskFactory();
    Task t1 = tf.StartNew(TaskMethod);

    //using the task factory via a task
    Task t2 = Task.TaskFactory.StartNew(TaskMethod);
     

    补充(Timer)

    适用于需周期性调用的方法,它不在创建计时器的线程中运行,它在由系统自动分配的单独线程中运行。这和Win32中的SetTimer方法类似。它的构造为:

    public Timer(
    TimerCallback callback,//所需调用的方法
    object state,//传递给callback的参数
    int dueTime,//多久后开始调用callback
    int period//调用此方法的时间间隔
    );
    // 如果 dueTime 为0,则 callback 立即执行它的首次调用。
    // 如果 dueTime 为 Infinite,则 callback 不调用它的方法,计时器被禁用。
    // 但使用 Change 方法可以重新启用它。
    // 如果period 为0或 Infinite,并且 dueTime 不为 Infinite,则 callback 调用它的方法一次。计时器的定期行为被禁用
    // 但使用 Change 方法可以重新启用它。
    // 如果 period 为零0或 Infinite,并且 dueTime 不为 Infinite,则 callback 调用它的方法一次。计时器的定期行为被禁用。
    // 但使用 Change 方法可以重新启用它。
    改变它的period和dueTime,我们可以通过调用Timer的Change方法来改变:

    public bool Change(
    int dueTime,
    int period
    );
    调用写法

    Timer tm=new Timer (new TimerCallback (CallBack),"Test",2000,2000);
    tm.Change (0,500);
    注意:

    线程调用执行的程序,如果对界面 UI 有做操作,会造成一定的界面异常现象,这时候就需要使用到委托,将需要对界面UI操作的内容放到委托去执行,这操作可叫做卸载委托。这里的委托可以有两种,一种是控件异步委托,一种是同步委托,写法如下:

    异步

    this.BeginInvoke((EventHandler)delegate
    {
    //UI操作
    });
    同步

    this.Invoke((EventHandler)delegate
    {
    //UI操作
    });
    ---------------------
    作者:玲妹妹的辉哥哥
    来源:CSDN
    原文:https://blog.csdn.net/qwerdfcv/article/details/80292840
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    正则表达式进阶——你从没想过的正则表达式使用方式
    bert 预训练模型路径
    Ubuntu 18.04 美化
    循环中存在异步的情况
    SQL truncate 、delete与drop区别
    用express创建网站出现"$ DEBUG=microbog ./bin/www"的提示
    全局安装了express框架,但是无法使用express指令的问题
    jQuery获取textarea中的文本
    js实现查询关键词,使其高亮
    SQL Server常用函数汇总
  • 原文地址:https://www.cnblogs.com/asdyzh/p/9860294.html
Copyright © 2020-2023  润新知