美文网首页.NET多线程
.NET多线程(二)线程

.NET多线程(二)线程

作者: 万州大牛 | 来源:发表于2017-01-21 13:43 被阅读0次

    本节主要内容


    命名空间

    System.Threading.Thread
    

    线程的创建

    使用构造函数

    static void Main(string[] args)
    {
        Thread thread = new Thread(SayHi);
        thread.Start();
    }
    static void SayHi()
    {
        Console.WriteLine("Hi.");
    }
    

    线程的挂起、阻塞、终止

    将【当前线程】挂起指定的时间。

    Thread.Sleep(1000);
    

    将【调用线程】阻塞,可以设置超时。也就是代码执行到这里后,将等待【thread线程】执行完成。

    thread.Join();
    

    终止【thread线程】,不推荐使用,不安全不可控。后面将介绍 CancellationToken 替代取消线程执行。

    thread.Abort();
    

    综合示例

    Thread thread = new Thread(() =>
    {
        // 挂起【thread线程】
        Thread.Sleep(3 * 1000);
        Console.WriteLine("1");
    });
    thread.Start();
    // 阻塞【调用线程】,等待【thread线程】
    thread.Join();
    Console.WriteLine("2");
    // 终止【thread线程】
    thread.Abort();
    Console.WriteLine("3");
    

    这里使用了拉姆达表达式创建线程。

    个人理解,这是拉姆达表达式的基本原型。小括号里是参数,大括号里是方法体。整个一起就是一个匿名委托。

    // 参数1个时,可以省略小括号
    // 方法体内语句1条时,可以省略大括号
    // 方法体內语句1条时,有返回值,可以省略return
    x => x == "hi"
    

    前台线程、后台线程

    默认前台

    Thread thread = new Thread(() =>
    {
        // 默认为 False
        Console.WriteLine(Thread.CurrentThread.IsBackground);
    });
    // 可以设置为后台线程
    //thread.IsBackground = true;
    thread.Start();
    thread.Join();
    

    重要性
    (1)有前台,不关闭
    如果线程为前台线程,可能导致UI线程已关闭,但实际还有前台线程暗地里运行,所以程序并没有真正关闭。
    (2)无前台,全关闭
    当然也要注意,如果所有前台线程都关闭,后台线程会自动关闭,后台线程的代码逻辑可能没执行完就终止了。

    推荐:把线程设置为后台线程
    

    线程传递参数

    (1)使用 ParameterizedThreadStart 委托

    static void Main(string[] args)
    {
        Thread thread = new Thread(SayHi);
        object obj = "Hi.";
        thread.Start(obj);
        thread.Join();
    }
    static void SayHi(object obj)
    {
        Console.WriteLine(obj.ToString());
    }
    

    (2)使用实例属性作为参数,实例方法作为线程入口

    static void Main(string[] args)
    {
        HiClass hiClass = new HiClass();
        hiClass.Hi = "Hi";
        Thread thread = new Thread(hiClass.SayHi);
        thread.Start();
        thread.Join();
    }
    class HiClass
    {
        public string Hi { get; set; }
        public void SayHi()
        {
            Console.WriteLine(Hi);
        }
    }
    

    (3)使用拉姆达,闭包

    下面的代码是(2)代码的翻版,使用拉姆达,闭包自动完成HiClass的创建,实例化,属性和方法操作。

    static void Main(string[] args)
    {
        string hi = "Hi";
        Thread thread = new Thread(() =>
        {
            //Thread.Sleep(4 * 1000);
            Console.WriteLine(hi);
        });
        thread.Start();
        Thread.Sleep(3 * 1000);
        hi = "Hello";
        thread.Join();
    }
    

    这里要注意多线程的时间竞争机制,hi在后续被重新赋值后,会污染thread线程类对hi的期望值。


    线程异常

    (1)对创建线程的代码(线程外部)包裹try catch是没用的

    static void Main(string[] args)
    {
        try // 无用的 try catch
        {
            Thread thread = new Thread(SayHi);
            thread.Start();
        }
        catch (Exception ex)
        {
            // 不会打印错误
            Console.WriteLine(ex.ToString());
        }
    }
    static void SayHi()
    {
        Console.WriteLine("hi");
        throw new Exception("error");
    }
    

    正确的姿势

    static void Main(string[] args)
    {
        Thread thread = new Thread(SayHi);
        thread.Start();
        thread.Join();
    }
    static void SayHi()
    {
        try
        {
            Console.WriteLine("hi");
            throw new Exception("error");
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
    

    初遇线程安全

    试试下面的代码,你会发现,张柏芝有时候会等于苍井空,苍井空有时候也会等于张柏芝。

    static void Main(string[] args)
    {
        Thread thread = new Thread(
            () => { Check("张柏芝"); });
        Thread thread2 = new Thread(
            () => { Check("苍井空"); });
        thread.Start();
        thread2.Start();
        thread.Join();
        thread2.Join();
    }
    static void Check(string name)
    {
        User.Name = name;
        string format = @"{0} = {1}";
        for (int i = 0; i < 10; i++)
        {
            string s = string.Format(format, name, User.Name);
            Console.WriteLine(s);
            Thread.Sleep(10);
        }
    }
    public class User
    {
        public static string Name { get; set; }
    }
    

    原因
    大家都很清楚,User.Name是静态属性,也就是共享资源,多个线程访问共享资源,需要对共享资源做同步处理。

    使用lock关键字

    static readonly object obj = new object();
    // -------------------------
    lock (obj)
    {
        User.Name = name;
        string format = @"{0} = {1}";
        for (int i = 0; i < 10; i++)
        {
            string s = string.Format(format, name, User.Name);
            Console.WriteLine(s);
            Thread.Sleep(10);
        }
    }
    

    对线程其他问题的思考

    (1)线程占用一定资源,比如内存;创建和销毁操作都比较昂贵
    (2)线程调度器thread scheduler要管理线程
    (3)大量的创建线程,导致内存不够用,线程调度器忙碌


    以上内容,仅代表个人理解,以及参考资料观点。
    如果有不同观点,请评论回复讨论交流。

    相关文章

      网友评论

        本文标题:.NET多线程(二)线程

        本文链接:https://www.haomeiwen.com/subject/hxjgbttx.html