在最近的项目中, 各种await关键字到处乱串,所以很有必要来了解下await和async 异步编程。
首先来了解些基本的概念:
1. 构建一个异步函数:
1) 申明时加入async 或Async 修饰符
2)返回值定义为 Task (普通函数中的void) 或 Task<T>(普通函数中的返回值)
3) 按约定,函数名以Async结尾 比如: async Task MethodName(); async Task<bool> Method Name();
2. 在异步函数中,需要包含await关键字
3. await 关键字:
函数遇到await关键字后会挂起当前函数,然后返回上一级调用函数, 直到await 后面的语句返回task or Task<T>, 然后继续await下面的语句。
http://msdn.microsoft.com/zh-cn/library/hh156528.aspx
接下来的几个典型场景,让我逐步了解这2个关键字的特性
1. 典型的异步调用
static void Main(string[] args)
{
Console.WriteLine("Thread {0} Main Function Starts", Thread.CurrentThread.ManagedThreadId);
AsyncClass asyncClass = new AsyncClass();
asyncClass.testSimpleAsync();
Console.WriteLine("Thread {0} Main Function Ends \n", Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(Timeout.Infinite);
}
/// 由于Main函数不能声明为异步方法,所以新建一个异步方法来调用目标异步方法
/// </summary>
public async Task testSimpleAsync()
{
Console.WriteLine("\nThread {0} testSimpleAsync starts", Thread.CurrentThread.ManagedThreadId);
Task displayCall = DisplayAsync();
Thread.Sleep(2);
await displayCall;
Console.WriteLine("Thread {0} testSimpleAsync complete \n", Thread.CurrentThread.ManagedThreadId);
}
/// <summary>
/// 目标异步方法
/// </summary>
/// <returns></returns>
private async Task DisplayAsync()
{
Console.WriteLine("\nThread {0} DisplayAsync starts", Thread.CurrentThread.ManagedThreadId);
await Task.Delay(10);
Console.WriteLine("Thread {0} DisplayAsync ends \n", Thread.CurrentThread.ManagedThreadId);
}
从输出结果可以看到, 异步方法在遇到了await关键字后, 会立即返回被调用函数(如在DisplayAsync()中遇到await, 立即返回被调用testSimpleAsync函数并执行testSimpleAysnc后面的语句),然后等待await后面的语句返回并执行后面的操作。
注意到这里的DispalyAsync ends 和 testSimpleAsync complete 在不同的线程上,但这不是由于await关键字创建新的线程给await后面的语句,而是由于后面的语句本身会产生新的线程。 在这里,是Task.Delay(10);产生了新的线程, 参考:http://social.technet.microsoft.com/wiki/contents/articles/21177.visual-c-thread-sleep-vs-task-delay.aspx
2. 让多个任务并发
public async void testConcurrencyCall_NewThreadAsync()
{
Console.WriteLine("testConcurrencyCall_NewThread Start");
List<Task> lstTasks = new List<Task>();
for (int i = 2; i <= 5; i++)
{
Console.WriteLine("{0}: testConcurrencyCall_NewThread before starting new task cycle-{1}", Thread.CurrentThread.ManagedThreadId, i);
Task task = Task.Factory.StartNew(() => DisplayAsync(i)); //DisplayAsync will run immediately at new thread.
Console.WriteLine("{0}: testConcurrencyCall_NewThread after starting new task cycle-{1}", Thread.CurrentThread.ManagedThreadId, i);
lstTasks.Add(task);
}
await Task.WhenAll(lstTasks);//Wait for all tasks complete.
Console.WriteLine("testConcurrencyCall_NewThread complete \n");
}
private async Task DisplayAsync(int i)
{
Console.WriteLine("{0}: {1}: cycle-{2}", Thread.CurrentThread.ManagedThreadId, "DisplayAsync starts", i);
await Task.Delay(3000);
Console.WriteLine("{0}: {1}: cycle-{2}", Thread.CurrentThread.ManagedThreadId, "DisplayAsync end", i);
}
Task.Factory.StartNew会开启一个新的线程跑异步方法, 在循环中将所有DisplayAsync触发完成, Task.WhenAll等待所有方法完成在继续下个操作。
这里提下Task.Run() 和Task.Factory.StartNew, 实际上都是开启一个新的线程跑方法, 只是 Task.Factory.StartNew的可选参数更多,Task.Run()用的是默认参数:http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx
上面的这个方式,实际上用System.Threading.Tasks.Parallel.For也可以完成同样的效果。
3. 把一个异步方法转化成同步方法:
#1
public void convertAsSync()
{
Task task = Task.Run(() => DisplayAsync());
task.Wait();
}
#2
public void convertAsSync()
{
Task task = DisplayAsync();
task.Wait();
}
Task.wait会等待Task完成后再继续后面的语句,可以把异步的方法DisplayAsync()转换成同步方法,不同的,
#1会开启一个新的线程跑整个DisplayAsync()
#2会在同个线程中调用DisplayAsync.
这就是几个基本Await和Async使用,要用好这2个关键字,要更加深入的了解Task和Task相关的方法(Run. WhenAll,AnyAll, ContinueWith等), 以后有时间再研究下。
参考链接:
http://msdn.microsoft.com/zh-cn/library/hh873191.aspx
http://msdn.microsoft.com/zh-cn/library/hh191443.aspx
http://tomasp.net/blog/csharp-async-gotchas.aspx/
http://www.codeproject.com/Articles/127291/C-vNext-New-Asynchronous-Pattern
http://msdn.microsoft.com/zh-cn/library/hh873191.aspx