Thread的问题
- 线程(Thread)时用来创建并发(concurrency)的一种低级别工具,它有一些限制,尤其是:
- 虽然开始线程的时候可以方便的传入数据,但是当Join的时候,很难从线程获得返回值。
- 可能需要设置一些共享字段。
- 如果操作抛出异常,捕获和传播该异常都很麻烦。
- 无法告诉线程在结束时开始做另外的工作,你必须进行Join操作(在进程中阻塞当前的线程)
- 虽然开始线程的时候可以方便的传入数据,但是当Join的时候,很难从线程获得返回值。
- 很难使用较小的并发(concurrent)来组建大型的并发。
- 导致了对手动同步的更大依赖以及随之而来的问题。
Task Class
- Task类可以很好的解决上述问题
- Task是一个相对高级的抽象:它代表了一个并发操作(concurrent)
- 该操作可能由Thread支持,或不由Thread支持
- Task是可组合的(可使用Continuation)把它们串成链
- Tasks可以使用线程池来减少启动延迟
- 使用TskCompletionSource,Tasks可以利用回调的方式,在等待I/O绑定时完全避免线程。
开始一个Task => Task.Run
- Task类在System.Threading.Tasks命名空间下。
开始一个Task最简单的办法就是使用Task.Run(.NET 4.5,4.0的时候时使用Task.Factory.StartNew)这个静态方法:
- 传入一个Action委托即可
class Program
{
static void Main(string[] args)
{
Task.Run(() => { Console.WriteLine("Foo"); });
}
}
- 传入一个Action委托即可
Task模式使用线程池,也就是后台线程:
- 当主线程结束的时候,你创建的所有Tasks都会结束。
class Program
{
static void Main(string[] args)
{
// 后台线程
Task.Run(() => { Console.WriteLine("Foo"); });
// 因为上面示例中,主线程走完了,控制台还没有执行输出,这使用ReadKey达到阻塞的目的。
Console.ReadKey();
}
}
- 当主线程结束的时候,你创建的所有Tasks都会结束。
Task.Run返回一个Task对象,可以使用它来监视其过程
- 在Task.Run之后,我们没有调用Start,因为该方法创建的是“热”任务(hot task)
- 可以通过Task的构造函数创建“冷”任务(cold task),但是很少这样做
- 在Task.Run之后,我们没有调用Start,因为该方法创建的是“热”任务(hot task)
- 可以通过Task的Status属性来跟踪task的执行状态。
Wait => 等待
调用Task的Wait方法会进行阻塞直到操作完成
- 相当于调用Thread上的Join方法
例子
class Program
{
static void Main(string[] args)
{
Task task = Task.Run(() =>
{
Thread.Sleep(3000);
Console.WriteLine("Foo");
});
Console.WriteLine(task.IsCompleted); // false
// 阻塞直至 task 完成操作
task.Wait();
Console.WriteLine(task.IsCompleted);// true
}
}
Wait也可以让你指定一个超时时间和一个取消令牌来提前结束等待。
Long-running tasks => 长时间运行的任务
- 默认情况下,CLR在线程池中运行Task,这非常适合短时间运行的Compute-Bound类工作。
针对长时间运行的任务或阻塞操作(前面的例子),你可以不采用线程池,例子。
class Program
{
static void Main(string[] args)
{
Task task = Task.Factory.StartNew(() =>
{
Thread.Sleep(3000);
Console.WriteLine("Foo");
}, TaskCreationOptions.LongRunning);
}
}
如果同时运行多个long-running tasks(尤其是其中有处于阻塞状态的),那么性能将会受很大影响,这时有比TaskCreationOptions.LongRunning更好的办法:
- 如果任务是IO-Bound,TaskCompletionSource和异步函数可以让你用回调(Coninuations)代替线程来实现并发。
- 如果任务是Comptre-Bound,生产者/消费者队列允许你对任务的并发性进行限流,避免把其他线程和进程饿死。