最近花了近两周时间读完了C#本质论,这本书非常喜欢,但是到后面关于多线程和同步这块,读起来就感觉有些困难了,所以做了笔记,一方面防止忘记,另一方法如果有不正确的地方,十分欢喜各位前辈不吝赐教
什么是单线程
通过一个控制台程序来认识单线程
static void Main(string[] args)
{
var mainThread = Thread.CurrentThread;
}
在Console.WriteLine处添加一个断点,查看主线程属性
ApartmentSate:msdn的大致意思,相同对单元状态的线程之间可以相互访问对象,然而在.net中由clr以线程安全的方式管理所有共享资源
CurrentCulture和CurrentUICulture表示区域信息
ExecutionContext:封装线程相关的上下文信息
IsAlive:如果此线程已启动并且尚未正常终止或中止,则为 true;否则为 false
IsBackground:表示是否是后台线程
IsThreadPoolThread:表示是否是线程池线程
ManagedThreadId:托管线程的唯一标识符
更多可查msdn
小结:
关于线程的定义很多地方都有的,我想举一个例子,很多时候,我们人就是一个线程,早上起床,吃早饭,上班,下班......这一系列事情有序执行就是一个单线程,但是有的时候,一边听歌,一边看小说实际上就开启了第二个线程了,假如此时再写代码,那就是开启第三个线程了
使用Thread创建一个线程
const int Repetitions = 100;
static void Main(string[] args)
{
ThreadStart threadStart = DoWork;
Thread thread = new Thread(threadStart);
thread.Start();
//Main线程启动一个循环
for (int count = 0; count < Repetitions; count++)
{
Console.Write('-');
}
Console.WriteLine("(主线程最后一个语句...)");
}
static void DoWork()
{
for (int count = 0; count < Repetitions; count++)
{
Console.Write("+");
}
}
Ctrl+F5运行,可以看到,新创建的线程和Main线程中的循环是同步执行的(多启动几次,会有不一样的发现哦!)
那么问题来了,我们创建的线程执行完了吗?程序到底什么时候结束?为什么主线程最后一句话执行完了,创建的线程还在控制台输出?
修改一下程序,Ctrl+F5,多启动几次,会有不一样的发现哦!
const int Repetitions = 100;
static int index_thread = 0;
static int index_main = 0;
static void Main(string[] args)
{
ThreadStart threadStart = DoWork;
Thread thread = new Thread(threadStart);
thread.Start();
//Main线程启动一个循环
for (int count = 0; count < Repetitions; count++)
{
index_main++;
Console.Write('-');
}
Console.WriteLine($"
index_thread:{index_thread}");
Console.WriteLine($"index_main:{index_main}");
Console.WriteLine("(主线程最后一个语句...)");
}
static void DoWork()
{
for (int count = 0; count < Repetitions; count++)
{
index_thread++;
Console.Write("+");
if (count == Repetitions - 1)
{
Debug.Write("我创建的线程执行完成了.....................................
");
}
}
}
假如你是直接按F5,可以在Visual Studio输出栏看到
结论:
1.操作系统在所有前台线程(主线程和新创建的线程都是前台线程)结束后终止进程,虽然在控制台中输出的index_thread不总是100
2.主线程以外的线程执行情况是不确定的,
3.实际上,主线程会等待所有子线程(前台线程)结束后,结束主线程,关闭进程,结束程序
4.由于子主线程执行情况的不确定性,在主线程输出index_thread的时候,可能子线程循环结束了,也可能没结束,所以导致结果总是不为100
通过Join方法阻塞主线程,等待子线程执行结束
//省略部分代码
thread.Join();
Console.WriteLine($"
index_thread:{index_thread}");
这样,就可以保证在此之后,子线程已经运行结束了,每次输出的结果都为100
使用线程池
const int Repetitions = 1000;
static int index_thread = 0;
static int index_main = 0;
static void Main(string[] args)
{
WaitCallback waitCallBack = DoWork;
ThreadPool.QueueUserWorkItem(waitCallBack, '+');
//Main线程启动一个循环
for (int count = 0; count < Repetitions; count++)
{
index_main++;
Console.Write('-');
}
Console.WriteLine($"
index_thread:{index_thread}");
Console.WriteLine($"index_main:{index_main}");
Console.WriteLine("主线程最后一个语句");
}
private static void DoWork(object ch)
{
for (int count = 0; count < Repetitions; count++)
{
index_thread++;
Console.Write(ch);
if (count == Repetitions - 1)
{
Debug.Write("我创建的线程执行完成了.....................................
");
}
}
}
优点:
1.解决线程太多造成的性能方面的负面影响
2.高效的利用处理器
2.结合lambda使用委托,代码可以更精简
注意点
1.使用线程池创建的线程都是后台线程
2.不要使用线程池运行时间特别长的任务,尽量不要I/O受限
异步任务
static void Main(string[] args)
{
Task task = Task.Run(()
=>
{
var t = Thread.CurrentThread;
for (int count = 0; count < Repetitions; count++)
{
index_thread++;
Console.Write('+');
}
});
for (int count = 0; count < Repetitions; count++)
{
index_main++;
Console.Write('-');
}
//类似THread.Join方法
task.Wait();
Console.WriteLine($"
index_thread:{index_thread}");
Console.WriteLine($"index_main:{index_main}");
Console.WriteLine("Over");
Console.ReadLine();
}
Task是.Net Framwwork4引入的一个类库,它在使用上比Thread简单了,可控性又THreadPool强了,默认情况下,它也是从线程池中请求一个线程来执行任务.
与ThreadPool相同的是,当创建时(调用Run)启动,与Thread相同的是,可以通过Wait()方法阻塞上下文线程(主线程)等待任务执行完成
带返回值的异步任务
static void Main(string[] args)
{
Task<string> task = Task.Run(()
=> "string 类型 返回值");
for (int i = 0; i < 1000; i++)
{
if (task.IsCompleted)
{
Console.Write('任务完成了');
break;
}
Console.Write('.');
}
Console.WriteLine(task.Result);
}
泛型的Task表示该任务具有返回值,IsCompleted表示任务是否完成
要注意的是,调用Result属性的时候,会阻塞上下文进程(内部执行Wait())