本节主要内容
命名空间
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)大量的创建线程,导致内存不够用,线程调度器忙碌
以上内容,仅代表个人理解,以及参考资料观点。
如果有不同观点,请评论回复讨论交流。
网友评论