本篇同步发文在个人Blog: [读书笔记] Threading in C# - PART 1: GETTING STARTED
这阵子换了新工作环境,公司使用不少C# Thread相关的技术,而知名书籍C# in a Nutshell的作者Joseph Albahari,将C# Thread的技术教学都免费公开,因此会阅读他的教学文来撰写读书笔记,希望在工作专案或Side Project都有帮助到。
作者有一些程序码并非完整,我会尽量写出实际可执行的范例,且有些功能Net Core以後不支援,也会加上注明。以下正式开始。
Thread是独立的执行路径, 也能同时和其他Thread工作
C# Client程序(Console, wpf, winform等), CLR都会起单一的Main thread执行
被赋予工作的Thread, 只要那工作(function)完成, 该Thread也就结束,也无法重新工作
每个Thread会分配到记忆体独立的Stack区块, 所以function的变数能有地方储存
Thread如果参考到同一物件, 该物件的资料会共享. 如果用static的资料也一样是共享
但资料共享容易造成_Thread-Safe_的问题, 要特别处理, 像下面范例, 因为两个取到done的值都是false, 所以都会执行
using System;
using System.Threading;
class ThreadTestWithSharedData
{
private bool done = false;
static void Main()
{
ThreadTestWithSharedData test = new ThreadTestWithSharedData();
Thread t = new Thread(test.GoMaybeNotSafe);
t.Start();
test.GoMaybeNotSafe();
Console.Read();
}
public void Go()
{
if(!done){
done = true;
Console.WriteLine("done");
}
}
public void GoMaybeNotSafe()
{
if(!done){
Console.WriteLine("done");
done = true;
}
}
}
使用Exclusive lock, 只允许一个thread运算
当Thread被Blocked, 不会消耗CPU资源
使用Join可等待Thread完成
使用Sleep让当前Thread暂停指定的时间
不管是Join或Sleep, 都是_Blocked_
Thread.Sleep(0) 将目前Thread的运算时间放其, 将CPU时间交给别的Thread, 等同功能是 Thread.Yield()
用Sleep(0)或Yield, 可以用来找thread safety的问题, 假如把Yield填入程序任何地方且出现问题, 代表这程序码有Bug
在CLR里有个Thread Scheduler, 代表作业系统, 由它Thread的执行时间
单一处理器的系统, 切的time slice时间比switch context的时间还长
多处理器的系统, 切的time slice有concurrency, 可以同时执行多个thread
Thread如果被preempted(抢占), 代表它是被interrupted, 比如time-slicing
多个Thread可以执行在1个Process
Process之间是互相隔离
Thread之间互相分享Heap记忆体的资料
Maintaining a responsive user interface: 其他的Worker Thread可背後执行消耗的任务, 而Main(UI) Thread与User操作互动
Making efficient use of an otherwise blocked CPU:
Parallel programming: 在多核心/多处理器的环境, 多个执行绪能平行分担工作
Speculative(投机性) execution: 有些任务可以用多个演算法同时运算, 最终结果取最快运算完的.
Allowing requests to be processed simultaneously: .NET的Server功能(WCF、ASP.NET等) 收到Request, 会自动建立多执行绪来处理. Client也是可同样的作法.
强调多执行绪之间共用资料时, 都会有Bug的产生. 建议把多执行绪的逻辑能封装在独立的library, 也比较好测试
有些功能用太多执行绪不见得更快, 比如Disk IO, 只要几个thread读取 比 10几个thread还快
可以在Thread.Start(someArgs)代入该function的参数
也可以用ParameterizedThreadStart, 但是function的参数必须用object, 再另外转型
Lambda expressions and captured variables: 传参数要注意共用性的问题, 下面的输出可能是0223557799, 而不是0~9各出现一次, 原因是有时多个Thread对i会存取到一样的
for (int i = 0; i < 10; i++)
new Thread (() => Console.Write (i)).Start();
解决Captured variable的方法是指定变数:
for (int i = 0; i < 10; i++)
{
int temp = i;
new Thread (() => Console.Write (temp)).Start();
}
可以指定Thread的名字, 比较容易做Debug
用Thread.CurrentThread.Name = XXXX 指定名字
Thread预设建立是Foreground, 代表它执行完才会让App结束
指定Thread.IsBackground = true, App终止时并不会理会Background的thread而强制终止
如果在程序要结束且有finally的background thread, 这thread也会被忽略掉, 解决方法有2
用Join
如果是Pooled thread, 可用event wait handler
Priority决定thread的执行时间长度
小心使用Priority, 否则可能造成对其他thread取资源的starvation
如果Process的Priority很低, 即使调高Thread的Priority也是会被限制资源
Process有个RealTime的Priority, 这会几乎抢占所有作业系统的资源, 小心使用, 一般用High就好
如果要做RealTime的应用程序且包含使用者介面, 通常会拆开来, 使用者介面一个程序、後端运算是另一个程序, 彼此沟通用Remoting(WCF, Web Api之类)或memory-mapped files (C# in a Nutshell 有提到!! 没用过~~)
using System;
using System.Threading;
class ThreadThrowException
{
static void Main(string[] args){
try{
Thread t = new Thread(Go);
t.Start();
}
catch(Exception ex){
Console.WriteLine("Hi i am here" + ex.Message);
}
Console.Read();
}
static void Go()
{
throw new Exception("Null");
}
}
using System;
using System.Threading;
class ThreadThrowException2
{
static void Main(string[] args){
Thread t = new Thread(Go);
t.Start();
Console.Read();
}
static void Go()
{
try{
throw new Exception("Null");
}
catch(Exception ex){
Console.WriteLine("Hi i am here" + ex.Message);
}
}
}
Global的异常事件处理(WPF和Winform的Application.DispatcherUnhandledException和 Application.ThreadException), 只有Main UI thread抛出的异常才会处理, 其他Worker thread的异常要自己处理
AppDomain.CurrentDomain.UnhandledException会被任何异常触发, 但无法阻止後续程序的中止, 以下范例两个exception都会被UnhandledException捕捉, 但程序仍直接中止
using System;
using System.Threading;
class ThreadThrowExceptionWithAppDomainHandler
{
static void Main(string[] args){
AppDomain currentDomain = AppDomain.CurrentDomain;
currentDomain.UnhandledException += new UnhandledExceptionEventHandler(MyHandler);
try{
Thread t = new Thread(Go);
t.Start();
}
catch(Exception ex){
Console.WriteLine("Hi i am here" + ex.Message);
}
throw new Exception("TEST");
Console.Read();
}
static void Go()
{
throw new Exception("Null");
}
static void MyHandler(object s, UnhandledExceptionEventArgs args)
{
Exception e = (Exception) args.ExceptionObject;
Console.WriteLine("runtime terminating: {0} ", args.IsTerminating);
}
}
Task Parallel Library
ThreadPool.QueueUserWorkItem
asynchronous delegates (BeginXXXXX...)
BackgroundWorker
WCF, Remoting, ASP.NET, ASMX Web service等的应用程序Server
System.Timers.Timer和System.Threading.Timer
Net有用Async结尾的函式, 比如WebClient(使用event-based asynchronous pattern)和BeginXXXX开头的函式(asynchronous programming model pattern)
PLINQ
不能对Thread pool设定Name
thread pool都是background thread
block thread pool可能会造成一些潜在问题, 有一些优化的手法(比如ThreadPool.SetMinThreads)
Thread pool设过priority後, 任务执行完回收到pool会赋归成normal priority
可以用Thread.CurrentThread.IsThreadPoolThread 查看目前Thread是不是从pool来的
新的Task类别使用Thread pool更简单
非泛型的Task类别取代ThreadPool.QueueUserWorkItem
泛型的Task类别取代asynchronous delegate (BeginXXXXX...)
非泛型的Task类别用Task.Factory.StartNew
会回传一个Task物件, 可以用Wait()等待, 而Task指定的函式发生Exception时, 会捕捉到
如果不对Task物件做Wait, 而中间发生的Exception会造成程序中止 ( 这个用Console程序无法成功, 主程序没被中止)
Task的结果可用.Result取得该Task回传的结果
在Task取Result有Exception时, 会包装在AggregateException, 没处理的话会让程序中止
QueueUserWorkItem
像是new Thread一样, 代入void的function, 也能代入参数, 都包装在object
如果function有未处理的exception, 将造成程序中止
using System;
using System.Threading;
class QueueUserWorkItem
{
static void Main(string[] args){
ThreadPool.QueueUserWorkItem(Go);
ThreadPool.QueueUserWorkItem(Go, 12345);
Console.Read();
}
static void Go(object data)
{
Console.WriteLine("Hello " + data);
}
}
Asynchronous delegates
能够回传值, 基於IAsyncResult
Asynchronous delegate和asynchronous methods不一样, 有些函式库也是用BeginXXX/EndXXX开头
使用Asynchronous delegates的流程:
建立要被委托的函式, 必需指定成Func类别
用Func的BeginInvoke呼叫该函式, 会回传IAsyncResult
用Func的EndInvoke代入IAsyncResult变数, 将取得结果
using System;
using System.Threading;
class AsynchronousDelegate
{
static void Main(string[] args){
Func<string, int, string> task = Go;
IAsyncResult cookie = task.BeginInvoke("test", 123, null, null);
string result = task.EndInvoke(cookie);
Console.WriteLine("Result is " + result);
Console.Read();
}
static string Go(string name, int n)
{
return name + " and " + n.ToString();
}
}
如果事情还未完成, 会等它完成
接收回传值
将Exception抛回至Caller
技术上来讲, 如果函式没有要回传值, 可以不呼叫EndInvoke, 但内部造成的Exception要小心. 所以建议都呼叫EndInvoke
另一种用法是把处理运算结果写在另一个委托函式, 该函式接收IAsyncResult的参数. 而不是在Caller呼叫 EndInvoke
using System;
using System.Threading;
class AsynchronousDelegate2
{
static void Main(string[] args){
Func<string, int, string> task = Go;
task.BeginInvoke("test", 123, Done, task);
Console.Read();
}
static void Done(IAsyncResult cookie)
{
var target = (Func<string, int, string>) cookie.AsyncState;
string result = target.EndInvoke(cookie);
Console.WriteLine("Result is " + result);
}
static string Go(string name, int n)
{
return name + " and " + n.ToString();
}
}
ThreadPool.SetMaxThreads可以设置Thread pool最多的Thread数量
每个环境有预设的上限
Framework 4.0 & 32-bit 可设1023个
Framework 4.0 & 64-bit 可设32768个
Framework 3.5 可设每个核心250个
Framework 2.0 可设每个核心25个
ThreadPool.SetMinThreads能设置最小的Thread数量, 预设是每个core会有1个
SetMinThreads能优化的状况是, 因为建立Thread会有延迟, 但如果SetMinThreads指定X个, 这X个Thread不要有延迟.
<<: Day 8 - 目前(传统)的机器学习三步骤(3)-训练
>>: PHP & MySQL 连结资料库进行增、删、改、查
有趣的简写越来越多了,而且越来越长了... 我个人认为这个章节应该摆在对称加密和非对称密码的中间来介...
缘起 各位好~我是一个软件工程师,追求每年都要有不一样的进步,今年追求的是把自己的基底在打的更稳,之...
本文同步更新於blog Prototype Pattern 当创建实例的过程很昂贵或复杂时,透过拷...
现在的企业会使用一些管理系统来管理人力等资源,而这些管理系统通常都会有所谓的 权限设计 (Permi...
接下来我们要针对复杂度做介绍,首先要说的就是高手们常常说的「Big O」! 但是到底什麽是 big ...