Task任务简单了解及其的线程等待和延续、Await/Async关键字简述
什么是Task?
Task是用来实现多线程的类,在以前当版本中已经有了Thread及ThreadPool,为什么还要提出Task类呢,这是因为直接操作Thread及ThreadPool,向线程中传递参数,获取线程的返回值及线程当启停时都非常的麻烦,所以微软的工程师对Thread进行了再封装,这就是Task,可以这么说Task是架构在Thread之上的,所以多线程时Task是我们的首选。
任务的线程是后台线程。
本质上讲,任务就是一个线程池的增强版API 。
线程池的缺点
1、线程池获取结果并不方便
2、异步处理不方便
3、连续的异步操作不方便
目的:
场景1: 3个并发请求,通过3个接口获取数据,我们希望得到所有接口的数据进行下一步业务处理。
场景2: 3个并发请求,只要其中某一个完成了,就触发一个事件结果。
Task的声明
Task的声明有两种方式:
a.通过new 的方式来声明
Task obj = new Task();
b.通过Task.Factory.StartNew的方式来声明
Task.Factory.StartNew(MyMethod);
这两种声明方式的区别: 第一种声明方式开启线程必须使用obj.Start(), 而通过Task.Factory.StartNew的方式则不用。
任务的创建
1.构造函数创建
static void Main(string[] args)
{
Task task = new Task(() =>
{
// True 线程池线程
Console.WriteLine(Thread.CurrentThread.IsThreadPoolThread);
});
task.Start();
Console.ReadLine();
}
2.Task.Factory.StartNew创建
注意:任务工厂模式下,任务会立即调用执行。
static void Main(string[] args)
{
Task.Factory.StartNew(() =>
{
// True 线程池线程
Console.WriteLine(Thread.CurrentThread.IsThreadPoolThread);
});
Console.ReadLine();
}
3.不使用线程池设置来创建
static void Main(string[] args)
{
Task task = new Task(() =>
{
// False 不使用线程池
Console.WriteLine(Thread.CurrentThread.IsThreadPoolThread);
}, TaskCreationOptions.LongRunning);
task.Start();
Console.ReadLine();
}
获取任务返回值
Task<string> task = new Task<string>(() =>
{
DoSomething();
return "此处为执行完成的结果";
});
task.Start();
Console.WriteLine(task.Result);
任务的回调
task.ContinueWith
(1)支持1个 task 接多个ContinueWith ;
(2)ContinueWith返回下一代 task,下一代可以继续ContinueWith;
Task<string> task = new Task<string>(() =>
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
return "boy,";
});
task.ContinueWith((t) =>
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
Console.WriteLine(t.Result + "come on.");
});
task.Start();
Console.ReadLine();
Task的线程等待和延续
Task下的线程等待和延续主要有以下几类:
Wait
针对单个Task的实例,可以让某一个线程进入等待。
WaitAny
执行的所有线程中只要有一个线程结束,主线程就执行,否则就卡住。
WatiAll
表示任务列表中所有的任务都已经结束后,主线程恢复。
WhenAny
表示任务列表中任何一个任务结束后,配合ContinueWith进行回调触发。
WhenAll
表示任务列表中所有任务都结束后,配合ContinueWith进行回调触发。
注意:WhenAny和WhenAll,都是开启一个新线程执行,不卡顿主线程。
TaskFactory
TaskFactory可以开启线程,当然也对应线程的等待和延续。
ContinueWhenAny:等价于Task的WhenAny+ContinueWith
ContinueWhenAll:等价于Task的WhenAll+ContinueWith
实例代码:
//1.ContinueWhenAny
Task.Factory.ContinueWhenAny(taskList.ToArray(),m=>{
Console.WriteLine("")
})
//2.ContinueWhenAll
Task.Factory.ContinueWhenAll(taskList.ToArray(),m=>{
Console.WriteLine("")
})
完整代码演示,创建控制台:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
public static void Main(string[] args)
{
List<Task<string>> taskList = new List<Task<string>>();
Task<string> t1 = new Task<string>(() =>
{
return GetName();
});
Task<string> t2 = new Task<string>(() =>
{
return GetMongo();
});
taskList.Add(t1);
taskList.Add(t2);
t1.Start();
t2.Start();
Task.WhenAll(taskList.ToArray()).ContinueWith(x =>
{
Console.WriteLine("任务执行完毕");
Console.WriteLine(x.Result[0]);
Console.WriteLine(x.Result[1]);
});
Console.ReadLine();
}
public static string GetName()
{
Thread.Sleep(5000);
return "MSSQL";
}
public static string GetMongo()
{
Thread.Sleep(10000);
return "MongoDB";
}
}
}
结果是:代码运行时间为10秒,依次输出相应内容。
await和async关键字
说到Task的返回值就不得不说await和async关键字了。当函数使用async标记后,返回值必须为void,Task,Task<T>,当返回值为Task<T>时,函数内部只需要返回T类型,编译器会自动包装成Task<T>类型。await关键字必须在具有async标记的函数内使用。
举个例子:
static void Main(string[] args)
{
Console.WriteLine("调用主线程");
Task<string> s = Testasync();
Console.WriteLine(s.Result);
Console.ReadKey();
}
static async Task<string> Testasync()
{
Console.WriteLine("运行Task之前" + Thread.CurrentThread.ManagedThreadId);
Task<string> t= Task.Run<string>(() =>
{
Console.WriteLine("运行Task" + Thread.CurrentThread.ManagedThreadId);
Thread.Sleep();
return "我是测试线程";
});
Console.WriteLine("运行Task之后" + Thread.CurrentThread.ManagedThreadId);
var result = await t;
return result;
}
async/await是C#5.0引入的一个关键词,其目的是让异步编程边的更加简洁,也是为了顺应异步编程的大趋势。
功能
1.使用async/await能简单的创建异步方法,异步方法可以防止耗时操作阻塞当前线程。
2.使用async/await来构建异步方法,更加简洁。
语法
① 方法使用async作为修饰符
② 方法内部包含一个或者多个await表达式,表示可以异步完成的任务
③ 必须具备以下三种返回类型 void 、Task 、Task<T> ,其中后两种的返回对象标识未来完成的工作,调用方法和异步方法可以继续执行。
④异步方法的参数可以任意类型,但是不能为out和ref参数
⑤约定俗成,一般异步方法都是以 Async作为后缀的。
⑥ 除了方法之外,Lambda表达式和匿名函数也可以作为异步对象。
如果在async中没有出现await,那么该方法就会变成一个同步方法。
异步方法的控制流
1首先第一个await之前的部分,这部分应该是少量且无需长时间等待的代码。
2await表达式,表示需要被异步执行的任务,await可以有多个。
3在await表达式之后出现的方法的其余代码。
举例:
例子1:
public static async Task DoSomethingAsync()
{
Console.WriteLine("异步方法同步部分1");
long sum = 0;
sum = await Task.Run(() =>
{
Console.WriteLine("异步执行部分1,线程ID:{0}",Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(2000);
long result = 0;
for (var i = 0; i < 1000000000; i++)
{
result += i;
}
Console.WriteLine("异步执行部分结束");
return result;
});
Console.WriteLine("计算结果{0}", sum);
}
例子2:
public static async void DoSomethingAsync()
{
Task t1 = new Task(()=> {
Console.WriteLine("异步开始了,线程:{0}", Thread.CurrentThread.ManagedThreadId);
long result = 0;
for (int i = 0; i < 1000000000; i++)
{
result += i;
}
Console.WriteLine("异步结束,计算结果{0},线程{1}", result, Thread.CurrentThread.ManagedThreadId);
});
t1.Start();
await t1;
}
async/await和普通异步的对比
思考:实现一个程序,每次任务休眠5秒后,返回100,打印100
public static void DoSomething()
{
Console.WriteLine("异步方法开始1");
var aaa= await Task.Run(() =>
{
Thread.Sleep(5000);
return 100;
});
Console.WriteLine("计算结果{0}", aaa.result);
}
调用:
static void Main(string[] args)
{
DoSomething();
DoSomething();
DoSomething();
}
解释:上述代码运行后,是同步执行的,虽然Task会异步产生一个线程运行,但是由于Task中的返回值需要使用aaa.result获取,阻塞了线程,所以运行结果是同步的。
将上述代码改装为异步:
public static void DoSomething111()
{
Console.WriteLine("异步方法开始1");
//通过第一个异步方法得到结果
var aaa= Task.Run(() =>
{
Thread.Sleep(5000);
return 100;
});
aaa.ContinueWith(x =>
{
Console.WriteLine(x.Result);
});
}
解释:由于Task.Run()返回的是一个Task对象,所以如果不希望阻塞线程,那么可以调用ContinueWith进行回调处理,在回调中获取Task的返回值,ContinueWith不会阻塞主线程。
优雅的写法async/await:
public async static void DoSomething111()
{
Console.WriteLine("异步方法开始1");
//通过第一个异步方法得到结果
var aaa= await Task.Run(() =>
{
Thread.Sleep(5000);
return 100;
});
Console.WriteLine("结果是{0}", aaa);
}
解释:Task.Run()执行的是一个耗时任务,await标记表示等待任务结果,将结果赋值给aaa,没错,就是这么优雅。
上一篇: 2014 HTML5/CSS3热门动画特效TOP10_jquery
下一篇: 协议的一致性