.NET多线程

.NET多线程(二)线程

2017-01-21  本文已影响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)大量的创建线程,导致内存不够用,线程调度器忙碌


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

上一篇下一篇

猜你喜欢

热点阅读