zoukankan      html  css  js  c++  java
  • C# 多线程详解 Part.01(UI 线程、子线程)

    基本概念

    什么是进程?
           当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源。一个进程至少有一个主线程。


    什么是线程?
           线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数


    什么是多线程?
           多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。


    多线程的好处?
           可以提高 CPU 的利用率。在多线程程序中,一个线程必须等待的时候,CPU 可以运行其它的线程而不是等待,这样就大大提高了程序的效率。


    多线程的不利方面?
           线程也是程序,所以线程需要占用内存,线程越多占用内存也越多。
           多线程需要协调和管理,所以 CPU 需要花时间来跟踪线程。
           线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题。
           线程太多会导致控制太复杂,最终可能造成很多 Bug。

    static void Main(string[] args)
    {
        Thread.CurrentThread.Name = "It's Main Thread";
        Console.WriteLine(Thread.CurrentThread.Name + " [Status:" + Thread.CurrentThread.ThreadState + "]");
    }

    image

           通过 Thread 类的静态属性 CurrentThread 可以获取当前正在执行的线程。不管创建了多少个这个类的实例,但是类的静态属性在内存中只有一个。很容易理解 CurrentThread 为什么是静态的--虽然有多个线程同时存在,但是在某一个时刻,CPU 只能执行其中一个!
           在.net framework class library 中,所有与多线程机制应用相关的类都是放在 System.Threading 命名空间中。

    Thread 类有几个至关重要的方法:

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

     

    操纵一个线程

    static void Main(string[] args)
    {
        Console.WriteLine("Thread Start/Stop/Join Sample:");
        
        // 创建一个线程,使之执行 Beta 方法 
        Thread oThread = new Thread(Beta);
     
        // 实际上,Start 方法只是通知 CPU 此线程可以被执行,但具体执行时机则由 CPU 自行决定。
        oThread.Start();
        while (!oThread.IsAlive)
        {
            Thread.Sleep(1);
        }
     
        oThread.Abort();
        oThread.Join();
        Console.WriteLine();
        Console.WriteLine("Beta has finished");
        try
        {
            Console.WriteLine("Try to restart the Alpha.Beta thread");
            oThread.Start();
        }
        catch (ThreadStateException)
        {
            Console.WriteLine("ThreadStateException trying to restart Alpha.Beta. ");
            Console.WriteLine("Expected since aborted threads cannot be restarted.");
            Console.ReadLine();
        }
    }
     
    public static void Beta()
    {
        while (true)
        {
            Console.WriteLine("Beta is running in its own thread.");
        }
    }

    image

           试图用 Thread.Start() 方法重新启动线程 oThread,但显然 Abort() 方法带来的后果是不可恢复的终止线程,所以最后程序会抛出 ThreadStateException 异常。

     

           线程的优先级

           当线程之间争夺 CPU 时,CPU 按照线程的优先级给予服务。在 C# 应用程序中,用户可以设定 5 个不同的优先级,由高到低分别是 HighestAboveNormalNormalBelowNormalLowest在创建线程时如果不指定优先级,那么系统默认为 ThreadPriority.Normal。
           通过设定线程的优先级,我们可以安排一些相对重要的线程优先执行,例如对用户的响应等等。

    static void Main(string[] args)
    {
        Thread t1 = new Thread(() =>
        {
            System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
            watch.Start();
            for (int i = 0; i < 20000; i++)
            {
                // 模拟耗时工作
                var obj = new { name = "XXX", age = 37 };
                GC.Collect();
            }
            watch.Stop();
            Console.WriteLine("t1 finished[ {0} ]", watch.ElapsedMilliseconds);
        });
     
        Thread t2 = new Thread(() =>
        {
            System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
            watch.Start();
            for (int i = 0; i < 20000; i++)
            {
                var obj = new { name = "XXX", age = 37 };
                GC.Collect();
            }
            watch.Stop();
            Console.WriteLine("t2 finished[ {0} ]", watch.ElapsedMilliseconds);
        });
        t1.Priority = ThreadPriority.AboveNormal;
        t2.Priority = ThreadPriority.BelowNormal;
        t1.Start();
        t2.Start();
    }

    image

           线程的调度算法非常复杂。记住,优先级高的线程并不一定先执行,但 CPU 会将更多的时间片分给优先级高的线程,因此,在相同任务量的前提下,高优先级线程将会较快的完成任务。

     

     

    Winform 中多线程的应用

           在 Winform 程序总,一般称绘制窗体和响应用户的线程为主线程,或 UI 线程。单线程最显著的缺点是,当一个事件发生,程序进行一个耗时的运算动作时,UI 线程会出现假死现象,此时会无视对用户的响应。

           下面的代码会模拟一些不同的情况:

    void DoSomething()
    {
        for (int i = 0; i < 900000000; i++)
        {
     
        }
        MessageBox.Show("It's finished.");
    }
     
    void ShowStr(object obj)
    {
        var list = obj as List<string>;
        if (list != null)
        {
            foreach (var item in list)
            {
                MessageBox.Show(item.ToString());
            }                
        }
        else
            MessageBox.Show("null");
    }
     
    // UI 单线程,运行时窗体会卡死一段时间
    private void btnUI_Click(object sender, EventArgs e)
    {
        DoSomething();
    }
     
    // 调用无參函数,此时窗体能响应用户
    private void btnThreadA_Click(object sender, EventArgs e)
    {
        Thread thread = new Thread(DoSomething);
        thread.Start();
    }
     
    // 当所有前台线程都关闭时,后台线程将立即结束运行,无条件的关闭
    // 而前台线程运行时,即使关闭 Form 主程序,该线程仍将继续运行,直到计算完毕
    private void btnThreadB_Click(object sender, EventArgs e)
    {
        Thread thread = new Thread(DoSomething);
        thread.IsBackground = true;
        thread.Start();
    }
     
    // 调用有参函数
    private void btnThreadC_Click(object sender, EventArgs e)
    {
        Thread thread = new Thread(ShowStr);
        thread.Start(new List<string> { "Jacky", "Skysoot", "Sam" });
    }

    image

           要注意的是,线程在调用有参函数时,通过 Start() 方法传递了参数给指定委托,该委托又将参数传递给了该线程欲运行的函数。看微软 Thread 类定义的元数据:

    image 

           Thread 类的 4 个构造函数基本分为 2 类,有参和无參。而 ParameterizedThreadStart 委托定义的方法原型的参数为 Object 类型,这提高了传参最大的灵活性。当然,在被调用的函数内部,需要依据一定的约定将 Object 对象进行转型处理。

     

     

    绕圈子封装一个线程类进行函数和参数的传递

    // 可定义各类型委托 示例暂定一个
    public delegate void Do(object obj);
     
    public class Worker
    {
        Do method;
        object obj;
     
        private void Work()
        {
            method(obj);
        }
     
        // 创建工人线程时 new 出工人实例 并在线程上指定 Work()
        public static Thread CreateWorkerThread(Do method, object obj)
        {
            Worker worker = new Worker();
            worker.method = method;
            worker.obj = obj;
            Thread t = new Thread(worker.Work);
            return t;
        }
    }
     
    // 任务类
    public class Quest
    {
        public static void Quest1(object obj)
        {
            Console.WriteLine("工人开始:" + obj.ToString() + "
    ");
        }
     
        public static void Quest2(object obj)
        {
            string[] list = obj as string[];
            if (obj != null)
            {
                foreach (var item in list)
                {
                    Console.WriteLine("工人开始:" + item);
                }
            }
        }
    }
     
    public class Test
    {
        public static void Main(string[] args)
        {
            Thread t1 = Worker.CreateWorkerThread(Quest.Quest1, "搬砖");
            t1.Start();
            Thread t2 = Worker.CreateWorkerThread(Quest.Quest2, new string[] {"唱歌", "跳舞", "打台球" });
            t2.Start();
        }
    }

           这种封装只是一种启发的方式而已,并非模式。但封装委托后的好处在于,调用方可以灵活指定 Worker 类执行什么类型的任务,加工什么参数,而无需再去考虑其余事情。

  • 相关阅读:
    centos7没有ifconfig命令
    ubuntu建立快捷方式
    SIMTRACE环境搭建
    联想 Lenovo PWR-G60 无线掌中宝拆机
    初玩OpenWRT之编译TP-Link WR841N V8固件
    我有特殊的Windows激活姿势
    适用于WIFI Hacking的无线网卡推荐
    制作Wi-Fi Ducky远程HID攻击设备
    路由器固件安全分析技术(一)
    如何HACK无线家用警报器?
  • 原文地址:https://www.cnblogs.com/SkySoot/p/3494154.html
Copyright © 2011-2022 走看看