1. 线程池(ThreadPool)
为什么要使用线程池? 主要原因是创建和销毁一个线程的代价是昂贵的,会消耗较多的系统资源;线程池原理? 每个CLR只有一个线程池,线程池线程不是在CLR初始化时自动创建的,而是向线程池派发(dispatch)异步操作时,如果线程池中没有线程,则会创建一个新新线程,不同的是,这个线程不会被销毁,执行完后进入空闲状态,等待响应新的异步请求。 当然,如果一个线程池线程不做任何事情,也是一种资源浪费。所以,当一个线程空闲特定一段时间后,会自己醒来终止自己以释放资源。 请注意线程池中的线程都是后台线程,在所有的前台线程运行结束后,所有的后台线程将停止工作。 创建一个线程时,会将当前线程的上下文传递给新建线程,而收集和复制上下文信息会耗费一定的时间和性能,在不需要传递上下文的场景中,可以通过System.Threading.ExecutionContext类型中的SuppressFlow()方法和RestoreFlow()方法分别阻止和恢复上下文的传递或流动。使用线程池时的注意项?
线程池不适合需要长时间运行的作业,或者处理需要与其它线程同步的作业;避免在线程池中执行I/O首先的操作,这种任务应该使用TPL模型;不要手动设置线程池的最小和最大线程数,CLR会自动执行线程池的扩张和收缩,手动干预会使性能更差(目前默认是1000个线程); 线程池的两种使用方式:
通过异步编程模型(Asynchronous Programming Model,简称APM)展示怎样在线程池中异步的执行委托? 下面的方式为异步编程模型(这是.net历史中第一个异步编程模式),这里使用委托的BeginInvoke()方法来来运行该委托,BeginInvoke接收一个回调函数,会在任务执行完后被调用;现在这种APM编程模型使用的越来越少了,更多的是使用任务并行库(Task Parallel Library, 简称TPL)。
private delegate string RunOnThreadPool(out int threadId
);
static void Main(string[] args
)
{
IAsyncResult r
= poolDelegate
.BeginInvoke(out threadId
, Callback
, "委托异步调用");
r
.AsyncWaitHandle
.WaitOne();
string result
= poolDelegate
.EndInvoke(out threadId
, r
);
}
通过ThreadPool.QueueUserWorkItem()向线程池中放入异步操作?
static void Main(string[] args
)
{
ThreadPool
.QueueUserWorkItem(AsyncOperation
);
ThreadPool
.QueueUserWorkItem(AsyncOperation
, "async state");
ThreadPool
.QueueUserWorkItem(state
=>
{
WriteLine($
"Operation state: {state}");
WriteLine($
"工作线程 id: {CurrentThread.ManagedThreadId}");
}, "lambda state");
}
private static void AsyncOperation(object state
)
{
WriteLine($
"Operation state: {state ?? "(null)"}");
WriteLine($
"工作线程 id: {CurrentThread.ManagedThreadId}");
}
使用普通创建线程方式和线程池方式有何区别? 分别运行下面两个方法,其中普通线程执行了2s多,但是创建了500个线程,线程池执行了9s多,但是只创建了很少的线程,为操作系统节省了线程和内存空间,但是花费的时间较多;
static void UseThreads()
{
for (int i
= 0; i
< 500; i
++)
{
var thread
= new Thread(() =>
{
Sleep(TimeSpan
.FromSeconds(0.1));
});
thread
.Start();
}
}
static void UseThreadPool()
{
for (int i
= 0; i
< 500; i
++)
{
ThreadPool
.QueueUserWorkItem(c
=>
{
Sleep(TimeSpan
.FromSeconds(0.1));
});
}
}
如何通过CancellationTokenSource取消线程:
private CancellationTokenSource cts
= new CancellationTokenSource();
private void StartThread(object sender
, RoutedEventArgs e
)
{
ThreadPool
.QueueUserWorkItem(o
=> Count(cts
.Token
, 100));
}
private void Count(CancellationToken token
, Int32 countTo
)
{
for (Int32 count
= 0; count
< countTo
; count
++)
{
if (token
.IsCancellationRequested
)
{
this.Dispatcher
.Invoke(() =>
{
this.AutoAddTextBox
.Text
+= '\n' + "Count is canceled" + '\n';
});
break;
}
this.Dispatcher
.Invoke(() =>
{
this.AutoAddTextBox
.Text
+= count
.ToString() + " ";
});
Thread
.Sleep(100);
}
this.Dispatcher
.Invoke(() =>
{
this.AutoAddTextBox
.Text
+= "Count is Done!" + '\n';
});
}
private void CancelThread(object sender
, RoutedEventArgs e
)
{
cts
.Cancel();
}
当运行到第32次时取消线程。
BackgroundWorker组件介绍 BackgroundWorker是基于事件的异步编程模式( Event-based Asynchronous Pattern,简称EAP),是.net历史上第二种用来构造异步程序的方式,现在推荐使用TPL;该组件被应用于WPF中,通过它实现的代码可以直接与UI控制器交互;
2、任务(Task)
什么是任务并行库(TPL)? 为了实现线程的同步、异步、异常传递等问题,需要编写较多的代码,来达到正确性和健壮性,而且最大的问题是没有内建的机制让你知道操作在什么时候完成,也没有机制在操作完成时获取返回值。为此,.NET 4.0引入了一个关于异步操作的API——任务并行库(TPL)。TPL的内部使用了线程池,而且效率更高。在把线程归还到线程池之前,它会在同一个线程中顺序执行多个任务,减少任务上下文切换带来的时间浪费问题。其中,任务(Task)是对象,封装了要异步执行的操作。 TPL被认为是线程池之上的又一抽象层,其对程序员隐藏了与线程池交互的底层代码,并提供了更方便的细粒度API,TPL的核心概念是任务Task。Task创建的是线程池任务,Thread默认创建的是前台线程;线程池一般只运行执行时间较短的异步操作;新建Task的方法有3种,示例如下: 其中Task2中使用的是Run()静态方法,Task4中设置了LongRunning,表明需要长时间运行,因此不是线程池线程;注意,Task.Run方法只是Task.Factory.StartNew的一个快捷方式,但是后者有附加的选项;
public Main()
{
var task1
= new Task(() => TaskMethod("Task 1"));
task1
.Start();
Task
.Run(() => TaskMethod("Task 2"));
Task
.Factory
.StartNew(() => TaskMethod("Task 3"));
Task
.Factory
.StartNew(() => TaskMethod("Task 4"), TaskCreationOptions
.LongRunning
);
}
private void TaskMethod(string name
)
{
string str
= string.Format("{0} 线程id:{1},线程池中线程:{2}",
name
, Thread
.CurrentThread
.ManagedThreadId
, Thread
.CurrentThread
.IsThreadPoolThread
);
this.Dispatcher
.BeginInvoke(new Action(delegate
{
this.textbox
.Text
+= str
+ "\n";
}));
}
输出结果:
Task 1 线程id:
10,线程池中线程:
True
Task 2 线程id:
12,线程池中线程:
True
Task 3 线程id:
13,线程池中线程:
True
Task 4 线程id:
12,线程池中线程:False
Task的基本操作:
TaskMethod("主线程任务");
Task
<int> task
= CreateTask("Task 1");
task
.Start();
task
.Wait();
int result
= task
.Result
;
WriteLine($
"运算结果: {result}");
task
= CreateTask("Task 2");
task
.RunSynchronously();
result
= task
.Result
;
WriteLine($
"运算结果:{result}");
一个伸缩性好的程序不应该使线程阻塞,当调用Wait,查询任务的Result属性时,极有可能造成线程池创建新线程,增大的资源的消耗。ContinueWith方法可以在任务完成时执行另一个任务,避免线程的阻塞。
private void StartThread(object sender
, RoutedEventArgs e
)
{
Task
<int> t
= new Task<int>(n
=> Sum((int)n
), 1000);
t
.Start();
Console
.WriteLine("before result out");
Task cwt
= t
.ContinueWith(task
=> Console
.WriteLine("result is : " + t
.Result
));
Console
.WriteLine("after result out");
}
private int Sum(int countTo
)
{
int sum
= 0;
for (int i
= 0; i
< countTo
; i
++)
{
sum
+= i
;
}
return sum
;
}
输出结果:
before result
out
after result
out
result
is : 499500
任务可以启动子任务,且只有当各子任务全部执行结束,父任务才结束:
Task
<int[]> parent
= new Task<int[]>(() =>
{
var results
= new int[3];
new Task(() => results
[0] = Sum(500), TaskCreationOptions
.AttachedToParent
).Start();
new Task(() => results
[1] = Sum(1000), TaskCreationOptions
.AttachedToParent
).Start();
new Task(() => results
[2] = Sum(1500), TaskCreationOptions
.AttachedToParent
).Start();
return results
;
});
parent
.ContinueWith(parentTask
=>
{
foreach (var item
in parent
.Result
)
{
Console
.WriteLine(item
.ToString());
}
});
parent
.Start();
输出结果:
124750
499500
1124250
Task相对于ThreadPool.QueueUserWorkItem具有很多附加属性,如任务状态,父任务的引用,TaskScheduler的引用,回调方法的引用等等,但会增加代价,因为需要为所有这些属性分配内存,所以在不需要这些附加功能时,采用ThreadPool.QueueUserWorkItem能获得更好的资源利用率。
System.Threading的Timer类 该类使一个线程池线线程定时调用一个方法。在内部,线程池为所有的Timer对象只使用了一个线程,即单独有一个线程控件所有Timer对象中回调方法的调用,当一个Timer对象到期时,该线程会在内部调用ThreadPool.QueueUserWorkItem,将回调任务添加到线程池队列中。当回调方法执行时间很长,而Timer间隔时间又很短时,会存在线程池多个线程同时执行一个回调方法。 所以要在一个线程池线程上执行定期性发生的后台任务时,采用Timer定时器。 System.Windows.Forms的Timer类及System.Windows.Threading的Dispatcher类的功能相同,与上面定时器的区别是:回调方法只由一个线程完成,就是设置定时器的线程,即在一个线程中设置了DispatcherTimer,那么其回调方法也只在该线程中执行。Task类中几个常用方法: public static bool WaitAll(Task[] tasks):判断tasks是否全部执行完毕; public static bool WaitAny(Task[] tasks):判断tasks中是否存在执行完毕的task; public static Task WhenAll(Task[] tasks):当所有tasks执行完毕后,创建并返回一个新的Task; public static Task WhenAll(Task[] tasks):当tasks中存在已执行完毕的task,创建并返回一个新的Task;TaskScheduler是一个非常重要的抽象,该组件实际上负责如何执行任务,默认的任务调试器将任务放置到线程池的工作线程中,这是TPL的默认选项;. C#5.0引入了新的语言特性,称为异步函数(asynchronous function),它是TPL之上更高级别的抽象,真正简化了异步编程,主要依靠async和await关键字实现;
参考:C#多线程编程系列 参考:C#多线程总结(纯干货)