c#await和wait与await的区别别

Posts - 63,
Articles - 0,
Comments - 596
技术笔记。
08:35 by 蘑菇先生, ... 阅读,
阅读目录:
Async、Await是net4.x新增的异步编程方式,其目的是为了简化异步程序编写,和之前APM方式简单对比如下。
APM方式,BeginGetRequestStream需要传入回调函数,线程碰到BeginXXX时会以非阻塞形式继续执行下面逻辑,完成后回调先前传入的函数。
HttpWebRequest myReq =(HttpWebRequest)WebRequest.Create("/");
myReq.BeginGetRequestStream();
Async方式,使用Async标记Async1为异步方法,用Await标记GetRequestStreamAsync表示方法内需要耗时的操作。主线程碰到await时会立即返回,继续以非阻塞形式执行主线程下面的逻辑。当await耗时操作完成时,继续执行Async1下面的逻辑
static async void Async1()
HttpWebRequest myReq = (HttpWebRequest)WebRequest.Create("/");
await myReq.GetRequestStreamAsync();
上面是net类库实现的异步,如果要实现自己方法异步。
public delegate int MyDelegate(int x);
MyDelegate mathDel = new MyDelegate((a) =& { return 1; });
mathDel.BeginInvoke(1, (a) =& { },null);
Async方式:
static async void Async2()
await Task.Run(() =& { Thread.Sleep(500); Console.WriteLine("bbb"); });
Console.WriteLine("ccc");
Console.WriteLine("aaa");
对比下来发现,async/await是非常简洁优美的,需要写的代码量更少,更符合人们编写习惯。
因为人的思维对线性步骤比较好理解的。
APM异步回调的执行步骤是:A逻辑-&假C回调逻辑-&B逻辑-&真C回调逻辑,这会在一定程度造成思维的混乱,当一个项目中出现大量的异步回调时,就会变的难以维护。
Async、Await的加入让原先这种混乱的步骤,重新拨正了,执行步骤是:A逻辑-&B逻辑-&C逻辑。
基本原理剖析
作为一个程序员的自我修养,刨根问底的好奇心是非常重要的。 Async刚出来时会让人有一头雾水的感觉,await怎么就直接返回了,微软怎么又出一套新的异步模型。那是因为习惯了之前的APM非线性方式导致的,现在重归线性步骤反而不好理解。 学习Async时候,可以利用已有的APM方式去理解,以下代码纯属虚构。
比如把Async2方法想象APM方式的Async3方法:
static async void Async3()
var task= await Task.Run(() =& { Thread.Sleep(500); Console.WriteLine("bbb"); });
//注册task完成后回调
task.RegisterCompletedCallBack(() =&
Console.WriteLine("ccc");
上面看其来就比较好理解些的,再把Async3方法想象Async4方法:
void Async4()
var thread = new Thread(() =&
Thread.Sleep(500);
Console.WriteLine("bbb");
//注册thread完成后回调
thread.RegisterCompletedCallBack(() =&
Console.WriteLine("ccc");
thread.Start();
这样看起来就非常简单明了,连async都去掉了,变成之前熟悉的编程习惯。虽然代码纯属虚构,但基本思想是相通的,差别在于实现细节上面。
内部实现剖析
作为一个程序员的自我修养,严谨更是不可少的态度。上面的基本思想虽然好理解了,但具体细节呢,编程是个来不得半点虚假的工作,那虚构的代码完全对不住看官们啊。
继续看Async2方法,反编译后的完整代码如下:
internal class Program
// Methods
[AsyncStateMachine(typeof(&Async2&d__2)), DebuggerStepThrough]
private static void Async2()
&Async2&d__2 d__;
d__.&&t__builder = AsyncVoidMethodBuilder.Create();
d__.&&1__state = -1;
d__.&&t__builder.Start&&Async2&d__2&(ref d__);
private static void Main(string[] args)
Console.WriteLine("aaa");
Console.ReadLine();
// Nested Types
[CompilerGenerated]
private struct &Async2&d__2 : IAsyncStateMachine
public int &&1__
public AsyncVoidMethodBuilder &&t__
private object &&t__
private TaskAwaiter &&u__$awaiter3;
// Methods
private void MoveNext()
bool flag = true;
switch (this.&&1__state)
goto Label_00C5;
if (Program.CS$&&9__CachedAnonymousMethodDelegate1 == null)
Program.CS$&&9__CachedAnonymousMethodDelegate1 = new Action(Program.&Async2&b__0);
awaiter = Task.Run(Program.CS$&&9__CachedAnonymousMethodDelegate1).GetAwaiter();
if (awaiter.IsCompleted)
goto Label_0090;
this.&&1__state = 0;
this.&&u__$awaiter3 =
this.&&t__builder.AwaitUnsafeOnCompleted&TaskAwaiter, Program.&Async2&d__2&(ref awaiter, ref this);
flag = false;
awaiter = this.&&u__$awaiter3;
this.&&u__$awaiter3 = new TaskAwaiter();
this.&&1__state = -1;
Label_0090:
awaiter.GetResult();
awaiter = new TaskAwaiter();
Console.WriteLine("ccc");
catch (Exception exception)
this.&&1__state = -2;
this.&&t__builder.SetException(exception);
Label_00C5:
this.&&1__state = -2;
this.&&t__builder.SetResult();
[DebuggerHidden]
private void SetStateMachine(IAsyncStateMachine param0)
this.&&t__builder.SetStateMachine(param0);
public delegate int MyDelegate(int x);
Collapse Methods
发现async、await不见了,原来又是编译器级别提供的语法糖优化,所以说async不算是全新的异步模型。 可以理解为async更多的是线性执行步骤的一种回归,专门用来简化异步代码编写。
从反编译后的代码看出编译器新生成一个继承IAsyncStateMachine
的状态机结构asyncd(代码中叫&Async2&d__2,后面简写AsyncD),下面是基于反编译后的代码来分析的。
IAsyncStateMachine最基本的状态机接口定义:
public interface IAsyncStateMachine
void MoveNext();
void SetStateMachine(IAsyncStateMachine stateMachine);
既然没有了async、await语法糖的阻碍,就可以把代码执行流程按线性顺序来理解,其整个执行步骤如下:
1. 主线程调用Async2()方法
2. Async2()方法内初始化状态机状态为-1,启动AsyncD
3. MoveNext方法内部开始执行,其task.run函数是把任务扔到线程池里,返回个可等待的任务句柄。MoveNext源码剖析:
//要执行任务的委托
Program.CS$&&9__CachedAnonymousMethodDelegate1 = new Action(Program.&Async2&b__0);
//开始使用task做异步,是net4.0基于任务task的编程方式。
awaiter =Task.Run(Program.CS$&&9__CachedAnonymousMethodDelegate1).GetAwaiter();
//设置状态为0,以便再次MoveNext直接break,执行switch后面的逻辑,典型的状态机模式。
this.&&1__state = 0;
//返回调用async2方法的线程,让其继续执行主线程后面的逻辑
this.&&t__builder.AwaitUnsafeOnCompleted&TaskAwaiter, Program.&Async2&d__2&(ref awaiter, ref this);
4. 这时就已经有2个线程在跑了,分别是主线程和Task.Run在跑的任务线程。
5. 执行主线程后面逻辑输出aaa,任务线程运行完成后输出bbb、在继续执行任务线程后面的业务逻辑输出ccc。
Label_0090:
awaiter.GetResult();
awaiter = new TaskAwaiter();
Console.WriteLine("ccc");
这里可以理解为async把整个主线程同步逻辑,分拆成二块。 第一块是在主线程直接执行,第二块是在任务线程完成后执行, 二块中间是任务线程在跑,其源码中awaiter.GetResult()就是在等待任务线程完成后去执行第二块。
从使用者角度来看执行步骤即为: 主线程A逻辑-&异步任务线程B逻辑-&主线程C逻辑。
Console.WriteLine("A逻辑");
static async void Test()
await Task.Run(() =& { Thread.Sleep(1000); Console.WriteLine("B逻辑"); });
Console.WriteLine("C逻辑");
回过头来对比下基本原理剖析小节中的虚构方法Async4(),发现区别在于一个是完成后回调,一个是等待完成后再执行,这也是实现异步最基本的两大类方式。
重点注意的地方
主线程A逻辑-&异步任务线程B逻辑-&主线程C逻辑。
注意:这3个步骤是有可能会使用同一个线程的,也可能会使用2个,甚至3个线程。&可以用Thread.CurrentThread.ManagedThreadId测试下得知。
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
static async void Async7()
await Task.Run(() =&
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
正由于此,才会有言论说Async不用开线程,也有说需要开线程的,从单一方面来讲都是对的,也都是错的。 上面源码是从简分析的,具体async内部会涉及到线程上下文切换,线程复用、调度等。 想深入的同学可以研究下ExecutionContextSwitcher、 SecurityContext.RestoreCurrentWI、ExecutionContext这几个东东。
其实具体的物理线程细节可以不用太关心,知道其【主线程A逻辑-&异步任务线程B逻辑-&主线程C逻辑】这个基本原理即可。 另外Async也会有线程开销的,所以要合理分业务场景去使用。
从逐渐剖析Async中发现,Net提供的异步方式基本上一脉相承的,如:
1. net4.5的Async,抛去语法糖就是Net4.0的Task+状态机。
2. net4.0的Task, 退化到3.5即是(Thread、ThreadPool)+实现的等待、取消等API操作。
本文以async为起点,简单剖析了其内部原理及实现,希望对大家有所帮助。.NET中的async和await关键字使用及Task异步调用实例
投稿:junjie
字体:[ ] 类型:转载 时间:
这篇文章主要介绍了.NET中的async和await关键字使用及Task异步调用实例,本文还包含了取消执行和显示进度的例子,需要的朋友可以参考下
其实早在.NET 4.5的时候M$就在.NET中引入了async和await关键字(VB为Async和Await)来简化异步调用的编程模式。我也早就体验过了,现在写一篇日志来记录一下顺便凑日志数量(以后面试之前可以用这个“复习”一下)。
(一)传统的异步调用
在比较“古老”的C#程序中经常可以看到IAsyncResult、BeginInvoke之类的异步调用“踪迹”。先来简单的复习一下吧。
假如我们有一个方法生成字符串,而生成这个字符串需要10秒中的时间:
public class WasteTimeObject
&&& public string GetSlowString(int begin, int length)
&&&&&&& StringBuilder sb = new StringBuilder();
&&&&&&& for (int i = i & begin + i++)
&&&&&&&&&&& sb.Append(WasteTime(i) + " ");
&&&&&&& return sb.ToString();
&&& private string WasteTime(int current)
&&&&&&& System.Threading.Thread.Sleep(1000);
&&&&&&& return current.ToString();
我们再做一个窗口,用来请求这个方法并把字符串显示到文本框中。使用同步调用肯定会把UI线程阻塞掉,要想不把UI阻塞掉就要另起一个线程了。基本的步骤如下:
创建一个异步调用的委托:
public delegate string GetSlowStringDelegate(int begin, int length);
然后呢,再异步调用这个委托:
private void button1_Click(object sender, EventArgs e)
&&& WasteTimeObject ad = new WasteTimeObject();
&&& GetSlowStringDelegate d = ad.GetSlowS
&&& textBox1.Text = "Requesting string, please wait...";
&&& IAsyncResult ar = d.BeginInvoke(1, 10, TaskComplete, d);
这里的BeginInvoke会在原来的基础上再附加两个参数:表示执行完毕后的回调方法AsyncCallBack,最后一个参数可以是任何对象,以便从回调方法中访问它。不过一般情况都是传递的委托实例,以便获取调用的结果。
当然我们也可以不用回调方法,这样就只好不断地循环查询是否执行完成了。
然后我们就要编写AsyncCallBack这个回调方法了,它接受一个IAsyncResult类型的对象表示异步调用的结果:
private void TaskComplete(IAsyncResult ar)
&&& if (ar == null)
&&& GetSlowStringDelegate d = ar.AsyncState as GetSlowStringD
&&& if (d == null) throw new Exception("Invalue object type");
&&& string result = d.EndInvoke(ar);
&&& this.Invoke(new Action(() =& UpdateTextResult(result)));
调用委托实例的EndInvoke方法并传入IAsyncResult类型的对象用以获取GetSlowString的返回结果。
回调方法是委托线程调用的,因此它不能直接访问UI,所以我们使用窗体的Invoke方法在主线程中显示结果。如果委托方法抛出异常,将会在EndInvoke时抛出。
(二)使用Task类型
可以看到使用传统的办法编写异步调用很麻烦,特别是如果这种调用很多,那么我们的程序就会变成很复杂,逻辑很乱。
.NET 4.5提供的新的异步变成模式就很好地解决了这个问题(其实本质上应该是.NET自动实现了很多操作),使编写异步代码和同步调用一样逻辑清晰。
首先来看看微软的例子:
private async Task SumPageSizesAsync()
&&& // To use the HttpClient type in desktop apps, you must include a using directive and add a
&&& // reference for the System.Net.Http namespace.
&&& HttpClient client = new HttpClient();
&&& // Equivalently, now that you see how it works, you can write the same thing in a single line.
&&& byte[] urlContents = await client.GetByteArrayAsync(url);
&&& // . . .
可以看出,使用await关键字后,.NET会自动把返回结果包装在一个Task类型的对象中。对于这个示例,方法是没有返回结果的。而对有返回结果的方法,就要使用Task&T&了:
public async Task&string& WaitAsynchronouslyAsync()
&&& await Task.Delay(10000);
&&& return "Finished";
总而言之,使用await表达式时,控制会返回到调用此方法的线程中;在await等待的方法执行完毕后,控制会自动返回到下面的语句中。发生异常时,异常会在await表达式中抛出。
对于我们这个例子,我们编写的代码如下:
private async void button1_Click(object sender, EventArgs e)
&&& textBox1.Text = "Requesting string, please wait...";
&&& WasteTimeObject ad = new WasteTimeObject();
&&& string result = await Task.Run(() =& ad.GetSlowString(1, 10));
&&& //Update UI to display the result
&&& textBox1.Text =
我们使用Task类新建一个工作线程并执行。当然我们也可以像M$给的例子那样改造一下GetSlowString,这样就不需要加上Task.Run了。(基本上,这种方法都会以Async后缀结尾。)
如何?原来的:创建异步委托→回调一气呵成。另外还有一点,await下面的语句是由主线程调用的,不是由新的线程调用,所以我们可以直接访问UI。
(三)取消执行和显示进度
最后一个要记录的,就是如何给异步调用添加进度条,并能让用户取消操作。界面就是下面这样:
使用最终完成的代码来说明吧。首先改造GetSlowString方法,使之支持取消和汇报进度:
public string GetSlowString(int begin, int length, IProgress&int& progress, CancellationToken cancel)
&&& StringBuilder sb = new StringBuilder();
&&& for (int i = i & begin + i++)
&&&&&&& sb.Append(WasteTime(i) + " ");
&&&&&&& cancel.ThrowIfCancellationRequested();
&&&&&&& if (progress != null)
&&&&&&&&&&& progress.Report((int)((double)(i - begin + 1) * 100 / length));
&&& return sb.ToString();
IProgress&T&类型的对象有一个Report方法,执行这个方法实际上会调用自定义的更新进度的方法,这个方法(使用委托或匿名方法皆可)是在生成Progress&T&对象的时候指定的:
IProgress&int& progress = new Progress&int&((progressValue) =& { progressBar1.Value = progressV });
神奇的是,这个方法是由主线程调用的,如果不是这样,它就不能更新我们界面上的控件。所以说微软提供的新机制帮我们简化了很多工作。
CancellationToken用于指定该方法“绑定”的取消上下文,如果这个对象执行过Cancel方法(用户点击了Cancel按钮),那么访问ThrowIfCancellationRequested时就会抛出OperationCanceledException类型的异常。这种机制的灵活性在于中止执行的位置是可以自行确定的,不会出现取消时自己都不知道执行到哪行代码的情况。
总而言之,单击request按钮的代码我们修改如下:
private async void button1_Click(object sender, EventArgs e)
&&& cancelSource = new CancellationTokenSource();
&&& IProgress&int& progress = new Progress&int&((progressValue) =& { progressBar1.Value = progressV });
&&& textBox1.Text = "Requesting string, please wait...";
&&& button1.Enabled = button2.Enabled =
&&& WasteTimeObject ad = new WasteTimeObject();
&&&&&&& string result = await Task.Run(() =& ad.GetSlowString(1, 10, progress, cancelSource.Token),
&&&&&&&&&&& cancelSource.Token);
&&&&&&& //Update UI to display the result
&&&&&&& textBox1.Text =
&&&&&&& button2.Enabled =& //Disable cancel button
&&& catch (OperationCanceledException)
&&&&&&& textBox1.Text = "You canceled the operation.";
取消按钮的代码就很简单了:
private void button2_Click(object sender, EventArgs e)
&&& if (cancelSource != null) cancelSource.Cancel();
&&& button2.Enabled =
至此,Task机制的初步体验就到此完成。以后有机会在研究下更高阶的内容吧。
您可能感兴趣的文章:
大家感兴趣的内容
12345678910
最近更新的内容
常用在线小工具async 和 await 出现在C# 5.0之后,给并行编程带来了不少的方便,特别是当在MVC中的Action也变成async之后,有点开始什么都是async的味道了。但是这也给我们编程埋下了一些隐患,有时候可能会产生一些我们自己都不知道怎么产生的Bug,特别是如果连线程基础没有理解的情况下,更不知道如何去处理了。那今天我们就来好好看看这两兄弟和他们的叔叔(Task)爷爷(Thread)们到底有什么区别和特点,本文将会对Thread 到 Task 再到 .NET 4.5的 async和 await,这三种方式下的并行编程作一个概括性的介绍包括:开启线程,线程结果返回,线程中止,线程中的异常处理等。
static void Main(){
new Thread(Go).Start();
// .NET 1.0开始就有的
Task.Factory.StartNew(Go); // .NET 4.0 引入了 TPL
Task.Run(new Action(Go)); // .NET 4.5 新增了一个Run的方法
public static void Go(){
Console.WriteLine("我是另一个线程");
  这里面需要注意的是,创建Thread的实例之后,需要手动调用它的Start方法将其启动。但是对于Task来说,StartNew和Run的同时,既会创建新的线程,并且会立即启动它。
  线程的创建是比较占用资源的一件事情,.NET 为我们提供了线程池来帮助我们创建和管理线程。Task是默认会直接使用线程池,但是Thread不会。如果我们不使用Task,又想用线程池的话,可以使用ThreadPool类。
static void Main() {
Console.WriteLine("我是主线程:Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
ThreadPool.QueueUserWorkItem(Go);
Console.ReadLine();
public static void Go(object data) {
Console.WriteLine("我是另一个线程:Thread Id {0}",Thread.CurrentThread.ManagedThreadId);
static void Main() {
new Thread(Go).Start("arg1"); // 没有匿名委托之前,我们只能这样传入一个object的参数
new Thread(delegate(){
// 有了匿名委托之后...
GoGoGo("arg1", "arg2", "arg3");
new Thread(() =& {
// 当然,还有 Lambada
GoGoGo("arg1","arg2","arg3");
}).Start();
Task.Run(() =&{
// Task能这么灵活,也是因为有了Lambda呀。
GoGoGo("arg1", "arg2", "arg3");
public static void Go(object name){
public static void GoGoGo(string arg1, string arg2, string arg3){
  Thead是不能返回值的,但是作为更高级的Task当然要弥补一下这个功能。
static void Main() {
// GetDayOfThisWeek 运行在另外一个线程中
var dayName = Task.Run&string&(() =& { return GetDayOfThisWeek(); });
Console.WriteLine("今天是:{0}",dayName.Result);
  上面说了参数和返回值,我们来看一下线程之间共享数据的问题。
private static bool _isDone =
static void Main(){
new Thread(Done).Start();
new Thread(Done).Start();
static void Done(){
if (!_isDone) {
_isDone = // 第二个线程来的时候,就不会再执行了(也不是绝对的,取决于计算机的CPU数量以及当时的运行情况)
Console.WriteLine("Done");
  线程之间可以通过static变量来共享数据。
&  我们先把上面的代码小小的调整一下,就知道什么是线程安全了。我们把Done方法中的两句话对换了一下位置 。
private static bool _isDone =
static void Main(){
new Thread(Done).Start();
new Thread(Done).Start();
Console.ReadLine();
static void Done(){
if (!_isDone) {
Console.WriteLine("Done"); // 猜猜这里面会被执行几次?
  上面这种情况不会一直发生,但是如果你运气好的话,就会中奖了。因为第一个线程还没有来得及把_isDone设置成true,第二个线程就进来了,而这不是我们想要的结果,在多个线程下,结果不是我们的预期结果,这就是线程不安全。
  要解决上面遇到的问题,我们就要用到锁。锁的类型有独占锁,互斥锁,以及读写锁等,我们这里就简单演示一下独占锁。
private static bool _isDone =
private static object _lock = new object();
static void Main(){
new Thread(Done).Start();
new Thread(Done).Start();
Console.ReadLine();
static void Done(){
lock (_lock){
if (!_isDone){
Console.WriteLine("Done"); // 猜猜这里面会被执行几次?
  再我们加上锁之后,被锁住的代码在同一个时间内只允许一个线程访问,其它的线程会被阻塞,只有等到这个锁被释放之后其它的线程才能执行被锁住的代码。
Semaphore 信号量
  我实在不知道这个单词应该怎么翻译,从官方的解释来看,我们可以这样理解。它可以控制对某一段代码或者对某个资源访问的线程的数量,超过这个数量之后,其它的线程就得等待,只有等现在有线程释放了之后,下面的线程才能访问。这个跟锁有相似的功能,只不过不是独占的,它允许一定数量的线程同时访问。
static SemaphoreSlim _sem = new SemaphoreSlim(3);
// 我们限制能同时访问的线程数量是3
static void Main(){
for (int i = 1; i &= 5; i++) new Thread(Enter).Start(i);
Console.ReadLine();
static void Enter(object id){
Console.WriteLine(id + " 开始排队...");
_sem.Wait();
Console.WriteLine(id + " 开始执行!");
Thread.Sleep(1000 * (int)id);
Console.WriteLine(id + " 执行完毕,离开!");
_sem.Release();
在最开始的时候,前3个排队之后就立即进入执行,但是4和5,只有等到有线程退出之后才可以执行。
  其它线程的异常,主线程可以捕获到么?
public static void Main(){
new Thread(Go).Start();
catch (Exception ex){
// 其它线程里面的异常,我们这里面是捕获不到的。
Console.WriteLine("Exception!");
static void Go() { }
  那么升级了的Task呢?
public static void Main(){
var task = Task.Run(() =& { Go(); });
task.Wait();
// 在调用了这句话之后,主线程才能捕获task里面的异常
// 对于有返回值的Task, 我们接收了它的返回值就不需要再调用Wait方法了
// GetName 里面的异常我们也可以捕获到
var task2 = Task.Run(() =& { return GetName(); });
var name = task2.R
catch (Exception ex){
Console.WriteLine("Exception!");
static void Go() { }
static string GetName() { }
一个小例子认识async & await
static void Main(string[] args){
Test(); // 这个方法其实是多余的, 本来可以直接写下面的方法
// await GetName()
// 但是由于控制台的入口方法不支持async,所有我们在入口方法里面不能 用 await
Console.WriteLine("Current Thread Id :{0}", Thread.CurrentThread.ManagedThreadId);
static async Task Test(){
// 方法打上async关键字,就可以用await调用同样打上async的方法
// await 后面的方法将在另外一个线程中执行
await GetName();
static async Task GetName(){
// Delay 方法来自于.net 4.5
await Task.Delay(1000);
// 返回值前面加 async 之后,方法里面就可以用await了
Console.WriteLine("Current Thread Id :{0}", Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("In antoher thread.....");
await 的原形
& await后的的执行顺序&
& & &感谢 locus的指正,&await 之后不会开启新的线程(await 从来不会开启新的线程),所以上面的图是有一点问题的。
  await 不会开启新的线程,当前线程会一直往下走直到遇到真正的Async方法(比如说HttpClient.GetStringAsync),这个方法的内部会用Task.Run或者Task.Factory.StartNew 去开启线程。也就是如果方法不是.NET为我们提供的Async方法,我们需要自己创建Task,才会真正的去创建线程。
static void Main(string[] args)
Console.WriteLine("Main Thread Id: {0}\r\n", Thread.CurrentThread.ManagedThreadId);
Console.ReadLine();
static async Task Test()
Console.WriteLine("Before calling GetName, Thread Id: {0}\r\n", Thread.CurrentThread.ManagedThreadId);
var name = GetName();
//我们这里没有用 await,所以下面的代码可以继续执行
// 但是如果上面是 await GetName(),下面的代码就不会立即执行,输出结果就不一样了。
Console.WriteLine("End calling GetName.\r\n");
Console.WriteLine("Get result from GetName: {0}", await name);
static async Task&string& GetName()
// 这里还是主线程
Console.WriteLine("Before calling Task.Run, current thread Id is: {0}", Thread.CurrentThread.ManagedThreadId);
return await Task.Run(() =&
Thread.Sleep(1000);
Console.WriteLine("'GetName' Thread Id: {0}", Thread.CurrentThread.ManagedThreadId);
return "Jesse";
  我们再来看一下那张图:
进入主线程开始执行
调用async方法,返回一个Task,注意这个时候另外一个线程已经开始运行,也就是GetName里面的 Task 已经开始工作了
主线程继续往下走
第3步和第4步是同时进行的,主线程并没有挂起等待
如果另一个线程已经执行完毕,name.IsCompleted=true,主线程仍然不用挂起,直接拿结果就可以了。如果另一个线程还同有执行完毕, name.IsCompleted=false,那么主线程会挂起等待,直到返回结果为止。
只有async方法在调用前才能加await么?
static void Main(){
Console.ReadLine();
static async void Test(){
Task&string& task = Task.Run(() =&{
Thread.Sleep(5000);
return "Hello World";
string str =
//5 秒之后才会执行这里
Console.WriteLine(str);
  答案很明显:await并不是针对于async的方法,而是针对async方法所返回给我们的Task,这也是为什么所有的async方法都必须返回给我们Task。所以我们同样可以在Task前面也加上await关键字,这样做实际上是告诉编译器我需要等这个Task的返回值或者等这个Task执行完毕之后才能继续往下走。
不用await关键字,如何确认Task执行完毕了?
static void Main(){
var task = Task.Run(() =&{
return GetName();
task.GetAwaiter().OnCompleted(() =&{
// 2 秒之后才会执行这里
var name = task.R
Console.WriteLine("My name is: " + name);
Console.WriteLine("主线程执行完毕");
Console.ReadLine();
static string GetName(){
Console.WriteLine("另外一个线程在获取名称");
Thread.Sleep(2000);
return "Jesse";
Task.GetAwaiter()和await Task 的区别?
加上await关键字之后,后面的代码会被挂起等待,直到task执行完毕有返回值的时候才会继续向下执行,这一段时间主线程会处于挂起状态。
GetAwaiter方法会返回一个awaitable的对象(继承了INotifyCompletion.OnCompleted方法)我们只是传递了一个委托进去,等task完成了就会执行这个委托,但是并不会影响主线程,下面的代码会立即执行。这也是为什么我们结果里面第一句话会是 &主线程执行完毕&!
Task如何让主线程挂起等待?
  上面的右边是属于没有挂起主线程的情况,和我们的await仍然有一点差别,那么在获取Task的结果前如何挂起主线程呢?
static void Main(){
var task = Task.Run(() =&{
return GetName();
var name = task.GetAwaiter().GetResult();
Console.WriteLine("My name is:{0}",name);
Console.WriteLine("主线程执行完毕");
Console.ReadLine();
static string GetName(){
Console.WriteLine("另外一个线程在获取名称");
Thread.Sleep(2000);
return "Jesse";
Task.GetAwait()方法会给我们返回一个awaitable的对象,通过调用这个对象的GetResult方法就会挂起主线程,当然也不是所有的情况都会挂起。还记得我们Task的特性么? 在一开始的时候就启动了另一个线程去执行这个Task,当我们调用它的结果的时候如果这个Task已经执行完毕,主线程是不用等待可以直接拿其结果的,如果没有执行完毕那主线程就得挂起等待了。
await 实质是在调用awaitable对象的GetResult方法
static async Task Test(){
Task&string& task = Task.Run(() =&{
Console.WriteLine("另一个线程在运行!");
// 这句话只会被执行一次
Thread.Sleep(2000);
return "Hello World";
// 这里主线程会挂起等待,直到task执行完毕我们拿到返回结果
var result = task.GetAwaiter().GetResult();
// 这里不会挂起等待,因为task已经执行完了,我们可以直接拿到结果
var result2 =
Console.WriteLine(str);
到此为止,await就真相大白了,欢迎点评。Enjoy Coding! :)&
阅读(...) 评论()

我要回帖

更多关于 java await wait 的文章

 

随机推荐