任务(Task)
Task作为C#异步的核心,Task位于System.Threading.Tasks命名空间下。
创建任务的基本原理是使用线程池,也就是说任务最终也是要交给线程去执行的。但是微软优化了任务的线程池,使线程的控制更加精准和高效.
task默认是后台线程,而thread默认是前台线程.可以设置task为前台线程,Thread.CurrentThread.IsBackground,将IsBackground设置为false即可.
Task出现之前,微软的多线程处理方式有:Thread→ThreadPool→委托的异步调用,虽然也可以基本业务需要的多线程场景,但它们在多个线程的等待处理方面、资源占用方面、线程延续和阻塞方面、线程的取消方面等都显得比较笨拙,ThreadPool相比Thread来说具备了很多优势,但是ThreadPool却又存在一些使用上的不方便。比如:
ThreadPool不支持线程的取消、完成、失败通知等交互性操作;
ThreadPool不支持线程执行的先后次序;
Task在ThreadPool的基础上进行的封装,Task的控制和扩展性很强,在线程的延续、阻塞、取消、超时等方面远胜于Thread和ThreadPool。
新建并启动task:
方式1:TaskFactory工厂
如果你想建立一个Task并且立即执行它,使用Task.Factory.StartNew(),这样做性能更好。
Task.Factory.StartNew(() =>
{
Console.WriteLine("Main2 Thread" + Thread.CurrentThread.ManagedThreadId);
});
//带有参数,返回值任务
Task<int>.Factory.StartNew(new Func<object, int>(test3), i);
static int test3(object obj)
{
int i = (int)obj;
Thread.Sleep(3000);
return i + i;
}
方式2:调用Start方法
如果你想建立一个Task,想在合适的时候执行它,那么使用Task构造器做初始化,使用Start函数启动任务。
//不带返回值,无参数的任务
Task task = new Task(() =>
{
Console.WriteLine("Main1 Thread" + Thread.CurrentThread.ManagedThreadId);
});
task.Start();
//带返回值,无参数的任务
Task<int> task1 = new Task<int>(() =>
{
Console.WriteLine("Main2 Thread" + Thread.CurrentThread.ManagedThreadId);
return 0;
});
task1.Start();
//带有参数,无返回值任务
Action<object> action = (a) =>
{
Console.WriteLine("The delegate method is executed,state is :" + a);
};
object obj2 = "success";
Task task2 = new Task(action, obj2);
task1.Start();
//带有参数,返回值任务
object i = 5;
Task<int> task3 = new Task<int>(new Func<object, int>(test3),i);
task3.Start();
static int test3(object obj)
{
int i = (int)obj;
Thread.Sleep(3000);
return i + i;
}
方式3:静态方法Run
//将任务放在线程池队列,返回并启动一个Task.task无参数,无返回值
Task task4 = Task.Run(() =>
{
Console.WriteLine("Main4 Thread" + Thread.CurrentThread.ManagedThreadId);
});
//将任务放在线程池队列,返回并启动一个Task.task无参数,带返回值
static int test5()
{
Thread.Sleep(3000);
return 0;
}
Task<int> task5 = Task.Run<int>(new Func<int>(test5));
求返回值
当task有返回值时,可通过public TResult Result { get; } Result 字段获取返回值。
如:int nret= task5.Result ;
使用Result 字段,会导致当前线程阻塞,直到task结束返回。
延伸到await :await 后面代码会阻塞,但当前线程不会阻塞。
等待task:
使用wait或WaitAll、WaitAny函数等待,可以设置等待时间。
Task task = new Task(() =>
{
Console.WriteLine("Main1 Thread" + Thread.CurrentThread.ManagedThreadId);
});
task.Start();
task.Wait(1000);
WaitAll为静态函数,可以设置等待多个任务结束,所有任务结束才返回。
Task<int> task1 = new Task<int>(() =>
{
Console.WriteLine("Main2 Thread" + Thread.CurrentThread.ManagedThreadId);
return 0;
});
task1.Start();
Task.WaitAll(task,task1 );
WaitAny静态函数,可以设置等待多个任务结束,只要一个任务结束就返回。
Task<int> task1 = new Task<int>(() =>
{
Console.WriteLine("Main2 Thread" + Thread.CurrentThread.ManagedThreadId);
return 0;
});
task1.Start();
Task.WaitAny(task,task1 );
WhenAny
WhenAny:与ContinueWith配合,线程列表中任何一个执行完毕,则继续ContinueWith中的任务(开启新线程,不阻塞主线程)
Action<string,int> log = (name,time) =>
{
Console.WriteLine($"{name}任务开始...");
Thread.Sleep(time);
Console.WriteLine($"{name}任务结束!");
};
List<Task> tasks = new List<Task>
{
Task.Run(() => log("张三",3000)),
Task.Run(() => log("李四",1000)),
Task.Run(() => log("王五",2000))
};
Task.WhenAny(tasks.ToArray()).ContinueWith(x => Console.WriteLine("某个Task执行完毕"));
连续任务
可以指定在任务完成之后,应开始运行之后另一个特定任务。ContinueWith是Task根据其自身状况,决定后续应该作何操作。
var testTask = new Task(() =>
{
Console.WriteLine("task start");
System.Threading.Thread.Sleep(2000);
});
testTask.Start();
var resultTest = testTask.ContinueWith<string>( task6 => {
Console.WriteLine("testTask end");
return "end";
});
Console.WriteLine(resultTest.Result);
取消任务
使用CancellationTokenSource:创建一个CancellationTokenSource对象,并将其传递给要取消的任务。然后,调用CancellationTokenSource的Cancel方法来触发任务的取消。
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
var task7 = Task.Factory.StartNew(() =>
{
System.Threading.Thread.Sleep(4000);
if (token.IsCancellationRequested)
{
Console.WriteLine("Abort mission success!");
return;
}
}, token);
//注册cancel后要执行的代码
token.Register(() =>
{
Console.WriteLine("Canceled");
});
Console.WriteLine("Press enter to cancel task...");
//调用取消
tokenSource.Cancel();