主要内容概要
1 await/async语法和使用
2 原理探究和使用建议
Await Async
await/async 是C# 的保留关键字 ,.Net framework4.5 版本出现的,通常是成对出现。
async修饰方法,可以单独出现,但是有警告。
await在方法体,只能出现在task/async方法前面,只有await会报错。await Task.Run()
和await task
这种用法都是可以的。
.NetFramework4.5—-await/async 语法糖:由编译器提供的功能。
Common Language Runtime(CLR)
只有async没有await
只有async没有await,跟普通方法没有区别:
private static async void NoReturnNoAwait()
{
//主线程执行
Console.WriteLine($"NoReturnNoAwait Sleep before Task,ThreadId={Thread.CurrentThread.ManagedThreadId}");
Task task = Task.Run(() =>//启动新线程完成任务
{
Console.WriteLine($"NoReturnNoAwait Sleep3000 before,ThreadId={Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(1000);
Console.WriteLine($"NoReturnNoAwait Sleep3000 after,ThreadId={Thread.CurrentThread.ManagedThreadId}");
});
//主线程执行
Console.WriteLine($"NoReturnNoAwait Sleep after Task,ThreadId={Thread.CurrentThread.ManagedThreadId}");
}
async/await 一起用
主线程调用async/await方法,主线程遇到await返回执行后续动作;
await 后面的代码会等着task任务的完成后再继续执行。
例子一:
主线程的内容:
Console.WriteLine($"当前主线程id={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
NoReturn();
Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
Console.Read();
子线程方法内容:
private static async void NoReturn()
{
//主线程执行
Console.WriteLine($"NoReturn Sleep before await,ThreadId={Thread.CurrentThread.ManagedThreadId}");
TaskFactory taskFactory = new TaskFactory();
Task task = taskFactory.StartNew(() =>
{
Console.WriteLine($"NoReturn Sleep3000 before,ThreadId={Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(3000);
Console.WriteLine($"NoReturn Sleep3000 after,ThreadId={Thread.CurrentThread.ManagedThreadId}");
});
await task;//主线程到这里就返回了,执行主线程任务
Console.WriteLine($"NoReturn Sleep after await,ThreadId={Thread.CurrentThread.ManagedThreadId}");
}
主线程在遇到await task
这里的时候就返回了,await后面的动作就被阻塞了,但是主线程没有被阻塞,继续在工作。
更进一步的理解:这个await可以理解为continuewith,主线程启动task没有被阻塞,回去干自己的活。然后ContinueWith就是一个回调,这个时候的线程可能是主线程,也可能是一个新的线程。Await在这里的作用就类似于把await后面的代码包装成一个回调。
值得注意的是:回调的动作,可能是Task线程,也可能是新的子线程,也可能是主线程。
task.ContinueWith(t => //这是一个回调
{
Console.WriteLine($"NoReturn Sleep after await,ThreadId={Thread.CurrentThread.ManagedThreadId}");
});
例子二:
一个async方法没有返回值的时候,可以返回Task。不同于NoReturn
方法,下面这个方法就是返回Task的:
private static async Task NoReturnTask() //在async/await方法里面如果没有返回值,默认返回一个Task
{
//这里还是主线程的id
Console.WriteLine($"NoReturnTask Sleep before await,ThreadId={Thread.CurrentThread.ManagedThreadId}");
Task task = Task.Run(() =>
{
Console.WriteLine($"NoReturnTask Sleep3000 before,ThreadId={Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(1000);
Console.WriteLine($"NoReturnTask Sleep3000 after,ThreadId={Thread.CurrentThread.ManagedThreadId}");
});
await task;
Console.WriteLine($"NoReturnTask Sleep after await,ThreadId={Thread.CurrentThread.ManagedThreadId}");
}
在主线程里面这么用:
Task t= NoReturnTask();
Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
t.Wait();//主线程等待Task的完成 阻塞的
await t;//await后的代码会由线程池的线程执行 非阻塞
带返回值的Task
带返回值的Task,要使用返回值就一定要等子线程计算完毕。
await/async用同步的方式编写代码,但又是非阻塞的。
private static async Task<long> SumAsync()
{
Console.WriteLine($"SumAsync 111 start ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
long result = 0;
await Task.Run(() =>
{
for (int k = 0; k < 10; k++)
{
Console.WriteLine($"SumAsync {k} await Task.Run ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(1000);
}
for (long i = 0; i < 999_999_999; i++)
{
result += i;
}
});
Console.WriteLine($"SumFactory 111 end ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
await Task.Run(() =>
{
for (int k = 0; k < 10; k++)
{
Console.WriteLine($"SumAsync {k} await Task.Run ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(1000);
}
for (long i = 0; i < 999999999; i++)
{
result += i;
}
});
}
主线程可以这么用,t.Result
和t.Wait()
是等价的,都是阻塞的。
Task<long> t = SumAsync();
Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
long lResult = t.Result;//访问result 主线程等待所有的任务挖成 //如果访问Result,就相当于是同步方法!
t.Wait();//等价于上一行
真的返回Task 不是async,要使用返回值就一定要等子线程计算完毕:
private static Task<int> SumFactory()
{
Console.WriteLine($"SumFactory 111 start ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
TaskFactory taskFactory = new TaskFactory();
Task<int> iResult = taskFactory.StartNew<int>(() =>
{
Thread.Sleep(3000);
Console.WriteLine($"SumFactory 123 Task.Run ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
return 123;
});
//Console.WriteLine($"This is {iResult.Result}");
Console.WriteLine($"SumFactory 111 end ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
return iResult;
}
Await Async原理
public static void Show()
{
Console.WriteLine($"start1 {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
Async();
Console.WriteLine($"aaa2 {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
}
public static async void Async()
{
Console.WriteLine($"ddd5 {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
await Task.Run(() =>
{
Thread.Sleep(500);
Console.WriteLine($"bbb3 {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
});
Console.WriteLine($"ccc4 {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
}
反编译一下可以看原理:
async方法在编译后会生成一个状态机(实现了IAsyncStateMachine接口);
异步状态机:初始化状态——执行就修改状态1——再执行就修改状态0——执行状态1——出现其他状态就结束了;
- async方法里面的逻辑其实都放在了MoveNext—主线程new一个状态机 状态-1
- 主线程调用MoveNext—执行了await之前的东西—启动Task—主线程改状态为0—回去干自己的事
- 子线程再去MoveNext—状态又回归-1—再执行后续的逻辑—如果需要还可以继续循环