C#多线程(4)Task的使⽤
⼀、Task的机制
在C#4.0之前需要执⾏⼀个复杂的异步操作时,只能使⽤CLR线程池技术来执⾏⼀个任务。线程池执⾏异步任务时,不知道任务何时完成,以及任务的在任务完成后不能获取到返回值。但是在C#4.0中引⼈了⼀个的任务(System.Threading.Tasks命名空间的类型)机制来解决异步操作完成时间和完成后返回值的问题。
其实现机制⼤致类似于线程池ThreadPool,不过对于ThreadPool来说Task的优势是很明显的:
ThreadPool的实现机制:(⼀对多)
1)应⽤程序拥有⼀个⽤于存放委托的全局队列;
2)使⽤ThreadPool.QueueUserWorkItem将新的委托加⼊到全局队列;
3)线程池中的多个线程按照先进先出的⽅式取出委托并执⾏。
Task的实现机制:(多对多)
writeline方法属于类
1)应⽤程序拥有⼀个⽤于存放Task(包装的委托)的全局队列(存放主程序创建的Task,标记为了
TaskCreationOptions.PreferFairness的Task),以及线程池中每个⼯作线程对应的本地队列(存放该⼯作线程创建的Task);
2)使⽤new Task()或Task.Factory.StartNew将新的Task加⼊到指定队列;
3)线程池中的多个线程按照优先处理本地队列,其次处理全局队列的⽅式取出Task并执⾏;
4)如果⼯作线程A发现本地队列为空(Task已处理完毕),那么A就会尝试去全局队列中获取Task,如果全局队列也为空,那么A就会到⼯作线程B的本地队列中“窃取”⼀个Task来执⾏,这种策略很明显的使得CPU更加充分的利⽤了并⾏执⾏。
⼆、Task的使⽤
2.1 使⽤Task类创建并执⾏简单任务
通过使⽤Task的构造函数来创建任务,并调⽤Start⽅法来启动任务并执⾏异步操作。创建任务时,必须传递⼀个Action或Action< object>类型的委托回调⽅法,可以选择的传递任务执⾏时说需要的数据对象等。
Task类的构造函数如下:
public Task(Action action);
public Task(Action<object> action, object state);
public Task(Action action, CancellationToken cancellationToken);
public Task(Action action, TaskCreationOptions creationOptions);
public Task(Action<object> action, object state, CancellationToken cancellationToken);
public Task(Action<object> action, object state, TaskCreationOptions creationOptions);
public Task(Action action, CancellationToken cancellationToken,
TaskCreationOptions creationOptions);
public Task(Action<object> action, object state,
CancellationToken cancellationToken, TaskCreationOptions creationOptions);
⽰例代码:
//新建⼀个Task
Task t1 = new Task(() => {
Console.WriteLine("Task完成!");
});
//启动Task
t1.Start();
Console.WriteLine("UI线程完成!");
上⾯是⽤new关键字创建,等同于如下使⽤Task.Factory(Task⼯⼚)创建的⽅式:
//新建⼀个Task(Task⼯⼚创建,⾃动启动)
Task t1 = Task.Factory.StartNew(() => {
Console.WriteLine("Task完成!");
});
Console.WriteLine("UI线程完成!");
这⾥为了简便使⽤了Lambda表达式(=> 为Lambda运算符),上⾯两部分代码都等同于如下:
void Test()
{
Console.WriteLine("Task完成!");
}
Action action = new Action(Test);
//新建⼀个Task
Task t1 = new Task(action);
/
/启动Task
t1.Start();
Console.WriteLine("UI线程完成!");
Task的执⾏⽅式有同步和异步两种,上⾯的⽅式很明显是异步执⾏,我们可以看到做为主线程的UI线程是先⼀步执⾏完的。那么要怎么样才能实现Task的同步执⾏呢?主要就这⼀个⽅法:Wait()!
代码如下:
//新建⼀个Task
Task t1 = new Task(() => {
Console.WriteLine("Task完成!");
});
//启动Task
t1.Start();
Console.WriteLine("UI线程开始等待!"); //①
t1.Wait();
Console.WriteLine("UI线程完成!"); //②
主线程运⾏到t1.Wait()时,会读取t1的状态,当发现任务t1还未执⾏结束时,主线程便会阻塞在这个位置(只是阻塞在t1.Wait()位置,也就是说t1.Wait()之前的代码①照旧执⾏),当读取到t1的状态为已经执⾏结束时,主线程才会再次恢复执⾏,从t1.Wait()之后的位置②继续往下执⾏。
当然,当有多个任务都需要保持同步执⾏时,可以使⽤Task.WaitAll⽅法同时等待多个任务完成,代码如下:
//新建⼀个Task
Task t1 = new Task(() => {
Console.WriteLine("Task1完成!");
});
/
/新建⼀个Task
Task t2 = new Task(() => {
Console.WriteLine("Task2完成!");
});
//启动Task
t1.Start();
t2.Start();
Console.WriteLine("UI线程开始等待!");
//等待t1,t2都完成
Task.WaitAll(t1,t2);
Console.WriteLine("UI线程完成!");
Task类还有⼀些静态⽅法,WaitAll⽤于等待提供的所有 System.Threading.Tasks.Task 对象完成执⾏过程和Wait⽤于等待提供的任⼀个System.Threading.Tasks.Task 对象完成执⾏过程,这两个⽅法都有⼀些重载版本。
//等待所有任务完成
public static void WaitAll(params Task[] tasks);
//等待任意⼀个任务完成
public static int WaitAny(params Task[] tasks);
2.2 等待任务的完成并获取返回值
使⽤任务执⾏异步操作时,最主要的是要后的任务完成时的返回值。在任务类中有⼀个实例⽅法Wait(有许多重载版本)他能等待任务的完成,我们也可以通过Task类的派⽣类Task< TResult>创建⼀个异步任务,并指定任务完成时返回值的类型,这样可以通过Task< TResult>的实例对象获取到任务完成后的返回值。
创建⼀个异步任务并执⾏0到100求和操作返回最后的计算结果,⽰例代码:
static void TaskWait()
{
//创建任务
Task<int> task = new Task<int>(() =>
{
int sum = 0;
Console.WriteLine("使⽤Task执⾏异步操作.");
for (int i = 0; i < 100; i++)
{
sum+=i;
}
return sum;
});
//启动任务,并安排到当前任务队列线程中执⾏任务(System.Threading.Tasks.TaskScheduler)
task.Start();
Console.WriteLine("主线程执⾏其他处理");
//等待任务的完成执⾏过程。
task.Wait();
//获得任务的执⾏结果
Console.WriteLine("任务执⾏结果:{0}", task.Result.ToString());
}
2.3 使⽤ContinueWith⽅法在任务完成时启动⼀个新任务
在使⽤能够Task类的Wait⽅法等待⼀个任务时或派⽣类的Result属性获得任务执⾏结果都有可能阻塞线程,为了解决这个问题可以使⽤ContinueWith⽅法,他能在⼀个任务完成时⾃动启动⼀个新的任务来处理执⾏结果。
⽰例代码:
static void TaskContinueWith()
{
//创建⼀个任务
Task<int> task = new Task<int>(() =>
{
int sum = 0;
Console.WriteLine("使⽤Task执⾏异步操作.");
for (int i = 0; i < 100; i++)
{
sum += i;
}
return sum;
});
//启动任务,并安排到当前任务队列线程中执⾏任务(System.Threading.Tasks.TaskScheduler)
task.Start();
Console.WriteLine("主线程执⾏其他处理");
//任务完成时执⾏处理。
Task cwt = task.ContinueWith(t => {
Console.WriteLine("任务完成后的执⾏结果:{0}", t.Result.ToString());
});
Thread.Sleep(1000);
}
上述⽰例中任务不是等待完成来显⽰执⾏结果,⽽是**使⽤ContinueWith⽅法,它能够知道任务在什么时候完成并启动⼀个新的任务来执⾏任务完成后的处理。**ContinueWith⽅法具有⼀些重载版本,这些重载版本允许指定延续任务需要使⽤的数据、延续任务的⼯作⽅式(System.Threading.Tasks.TaskContinuationOptions的枚举值按位OR运⾏的结果)等。
2.4 创建⽗⼦任务和任务⼯⼚的使⽤
通过Task类创建的任务是顶级任务,可以通过使⽤ TaskCreationOptions.AttachedToParent 标识把这些任务与创建他的任务相关联,所有⼦任务全部完成以后⽗任务才会结束操作。⽰例如下:
static void ParentChildTask()
{
Task<string[]> parent = new Task<string[]>(state => {
Console.WriteLine(state);
string[] result=new string[2];
//创建并启动⼦任务
new Task(() => { result[0]= "我是⼦任务1。";},
TaskCreationOptions.AttachedToParent).Start();
new Task(() => { result[1] = "我是⼦任务2。"; },
TaskCreationOptions.AttachedToParent).Start();
return result;
},"我是⽗任务,并在我的处理过程中创建多个⼦任务,所有⼦任务完成以后我才会结束执⾏。");
//任务处理完成后执⾏的操作
parent.ContinueWith(t => {
Array.ForEach(t.Result, r=>Console.WriteLine(r));
});
//启动⽗任务
parent.Start();
Console.Read();
}
如果需要创建⼀组具有相同状态的任务时,可以使⽤TaskFactory类或TaskFactory类。这两个类创建⼀组任务时可以指定任务的CancellationToken、TaskCreationOptions、TaskContinuationOptions和TaskScheduler默认值。⽰例代码:
static void TaskFactoryApply()
{
Task parent = new Task(() =>
{
CancellationTokenSource cts = new CancellationTokenSource(5000);
//创建任务⼯⼚
TaskFactory tf = new TaskFactory(cts.Token, TaskCreationOptions.AttachedToParent,
TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
//添加⼀组具有相同状态的⼦任务
Task[] task = new Task[]{
tf.StartNew(() => { Console.WriteLine("我是任务⼯⼚⾥的第⼀个任务。"); }),
tf.StartNew(() => { Console.WriteLine("我是任务⼯⼚⾥的第⼆个任务。"); }),
tf.StartNew(() => { Console.WriteLine("我是任务⼯⼚⾥的第三个任务。"); })
};
});
parent.Start();
Console.Read();
}
2.5 取消任务
我们知道task是并⾏计算的,⽐如说主线程在某个时刻由于某种原因要取消某个task的执⾏,我们能做到吗? 当然我们可以做到。在4.0中给我们提供⼀个“取消标记”叫做CancellationTokenSource.Token,在创建task的时候传⼊此参数,就可以将主线程和任务相
关联,然后在任务中设置“取消信号“叫做ThrowIfCancellationRequested来等待主线程使⽤Cancel来通知,⼀旦cancel被调⽤。task 将会抛出OperationCanceledException来中断此任务的执⾏,最后将当前task的Status的IsCanceled属性设为true。看起来是不是很抽象,没关系,上代码说话。
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
class Program
{
static void Main(string[] args)
{
var cts = new CancellationTokenSource();
var ct = cts.Token;
Task task1 = new Task(() => { Run1(ct); }, ct);
Task task2 = new Task(Run2);
try
{
task1.Start();
task2.Start();
Thread.Sleep(1000);
cts.Cancel();
Task.WaitAll(task1, task2);
}
catch (AggregateException ex)
{
foreach (var e in ex.InnerExceptions)
{
Console.WriteLine("\n hi,我是OperationCanceledException:{0}\n", e.Message);
}
//task1是否取消
Console.WriteLine("task1是不是被取消了? {0}", task1.IsCanceled);
Console.WriteLine("task2是不是被取消了? {0}", task2.IsCanceled);
}
Console.Read();
}
static void Run1(CancellationToken ct)
{
ct.ThrowIfCancellationRequested();
Console.WriteLine("我是任务1");
Thread.Sleep(2000);
ct.ThrowIfCancellationRequested();
Console.WriteLine("我是任务1的第⼆部分信息");
}
static void Run2()
{
Console.WriteLine("我是任务2");
}
}
运⾏结果分析:
1)Run1中的Console.WriteLine(“我是任务1的第⼆部分信息”); 没有被执⾏。
2)Console.WriteLine(“task1是不是被取消了? {0}”, task1.IsCanceled); 状态为True。也就告诉我们Run1中途被主线程中断执⾏,我们coding的代码起到效果了。
2.6 任务内部实现和任务调度
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论