C# 异步编程(async_await特性的结构)
async/await特性的结构
我们已经看到了一个异步方法的示例,现在来讨论其定义和细节。
如果一个程序调用某个方法,并在等待方法执行所有处理后才继续执行,我们就称这样的方法是同步的。这是默认的形式,在本章之前你所看到的都是这种形式。
相反,异步的方法在完成其所有工作之前就返回到调用方法。利用C#的async/await特性可
以创建并使用异步方法。该特性由3个部分组成,如图21-3所示。
- 调用方法(callingmethod):该方法调用异步方法,然后在异步方法执行其任务的时候继
续执行(可能在相同的线程上,也可能在不同的线程上)。 - 异步(async)方法:该方法异步执行其工作,然后立即返回到调用方法。
- a表达式:用于异步方法内部,指明需要异步执行的任务。一个异步方法可以包含任
意多个await表达式,不过如果一个都不包含的话编译器会发出警告。
后面几节会介绍这三个组件的细节,先从异步方法的语法和语义开始。
class Program //调用方法
{static void Main(){Task<int>value=DoAsyncStuff.CalculateSumAsync(5,6);}static class DoAsyncStuff //异步方法{public static async Task<int>CalculateSumAsync(int i1,int i2){int sum=await TaskEx.Run(()=>GetSum(i1,i2));return sum;}}
}
什么是异步方法
如上节所述,异步方法在完成其工作之前即返回到调用方法,然后在调用方法继续执行的时
候完成其工作。
在语法上,异步方法具有如下特点,如图21-4所示。
- 方法头中包含async方法修饰符。
- 包含一个或多个await表达式,表示可以异步完成的任务。
- 必须具备以下3种返回类型之一。第二种(Task)和第三种(Task)的返回对象表示
将在未来完成的工作,调用方法和异步方法可以继续执行。- void
- Task
- Task
- ValueTask
- 任何具有公开可访问的GetAwaiter方法的类型。我们很快就会谈到GetAwaiter。
- 异步方法的形参可以为任意类型、任意数量,但不能为out或ref参数。
- 按照约定,异步方法的名称应该以Async为后缀。
- 除了方法以外,Lambda表达式和匿名方法也可以作为异步对象。
async Task<T> CountCharactersAsync(int id,string site)
{Console.WriteLine("Starting CountCharacters");WebClient wc=new WebClient();string result=await wc.DownloadStringTaskAsync(new Uri(site));Console.WriteLine("CountCharacters Completed");return result.Length;
}
图21-4阐明了一个异步方法的组成部分,现在我们可以详细介绍了第一项是async关
键字。
- 异步方法的方法头中必须包含async关键字,且必须位于返回类型之前。
- 该修饰符只是标识该方法包含一个或多个await表达式。也就是说,它本身并不能创建任
何异步操作。 - async关键字是一个上下文关键字,也就是说除了作为方法修饰符(或Lambda表达式修
饰符、匿名方法修饰符)之外,async还可用作标识符。
返回类型必须是以下类型之一。注意,其中3种都包含Task类。我们在指明类的时候,将
使用大写形式(类名)和语法字体。在表示一系列需要完成的工作时,将使用小写字母和一般
字体。
- Task:如果调用方法不需要从异步方法中返回某个值,但需要检查异步方法的状态,那么
异步方法可以返回一个Task类型的对象。在这种情况下,如果异步方法中包含任何return
语句,则它们不能返回任何东西。下面的代码来自一个调用方法:
Task someTask=DoStuff.CalculateSumAsync(5,6);someTask.Wait();
- Task:如果调用方法要从调用中获取一个T类型的值,异步方法的返回类型就必须是
Task。调用方法将通过读取Task的Result属性来获取这个T类型的值。下面的代码
阐明了这一点:
Task<int>value=DoStuff.CalculateSumAsync(5,6);Console.WriteLine($"Value:{value.Result}");
ValueTask:这是一个值类型对象,它与Task类似,但用于任务结果可能已经可用的
情况。因为它是一个值类型,所以它可以放在栈上,而无须像Task对象那样在堆上分配空间。
因此,它在某些情况下可以提高性能。
-
void:如果调用方法仅仅想执行异步方法,而不需要与它做任何进一步的交互时[这称为
“调用并忘记”(fireandforget)],异步方法可以返回void类型。这时,与上一种情况类
似,如果异步方法中包含任何return语句,则它们不能返回任何东西。 -
任何具有可访问的GetAwaiter方法的类型。
注意在图21- 4中,异步方法的返回类型为Task。但方法体中不包含任何返回Task
类型对象的return语句。相反,方法最后的return语句返回了一个int类型的值。我们先将这
一发现总结如下,稍后再详细解释。 -
任何返回Task类型的异步方法,其返回值必须为T类型或可以隐式转换为T的类型。
图21-5、图21-6、图21-7和图21-8阐明了调用方法和异步方法在用这三种返回类型进行交
互时所需的体系结构。
使用返回Task对象的异步方法
using System;
using System.Threading.Tasks;class Program
{static void Main(){Task<int>value=DoAsyncStuff.CalcuateSumAsync(5,6);//处理其他事情Console.WriteLine("value:{0}",value.Result);}
}static class DoAsyncStuff
{public static async Task<int>CalcuateSumAsync(int i1,int i2){int sum=await Task.Run(()=>GetSum(i1,i2));return sum;}private static int GetSum(int i1,int i2){return i1+i2;}
}
使用返回对象的异步方法
using System;
using System.Threading.Tasks;class Program
{static void Main(){Task someTask=DoAsyncStuff.CalculateSumAsync(5,6);//处理其他事情someTask.Wait()Console.WriteLine("Async stuff is done");}
}static class DoAsyncStuff
{public static async Task CalculateSumAsync(int i1,int i2){int value=await Task.Run(()=>GetSum(i1,i2));Console.WriteLine("Value:{0}",value);}private static int GetSum(int i1,int i2){return i1+i2;}
}
图21-7中的代码使用了Thread.Sleep方法来暂停主线程,这样它就不会在异步方法完成之
前退出。
使用“调用并忘记”的异步方法
using System;
using System.Threading;
using Systme.Threading.Tasks;class Program
{static void Main(){DoAsyncStuff.CalculateSumAsync(5,6);//处理其他事情Thread.Sleep(200);Console.WriteLine("Program Exiting");}
}static class DoAsyncStuff
{public static async void CalculateSumAsync(int i1,int i2){int value=await Task.Run(()=>GetSum(i1,i2));Console.WriteLine("Value:{0}",value);}private static int GetSum(int i1,int i2){return i1_i2;}
}
图21- 8中的代码使用了ValueTask返回类型。
使用返回ValueTask对象的异步方法
using System;
using System.Threadingg.Tasks;class Program
{static void Main(){ValueTask<int>value=value=DoAsyncStuff.CalculateSumAsync(0,6);//处理其他事情Console.WriteLine($"Value:{value.Result}");value=DoAsyncStuff.CalculateSumAsync(5,6);//处理其他事情Console.WriteLine($"Value:{value.Result}");}static calss DoAsyncStuff{public static async ValueTask<int>CalculateSumAsync(int i1,int i2){if (i1==0)//如i1==0,则可以避免执行长时间运行的任务{return i2;}int sum=await Task<int>.Run(()=>GetSum(i1,i2));return sum;}private static int GetSum(int i1,int i2){return i1+i2;}}
}
异步方法的控制流
异步方法的结构包含三个不同的区域,如图21- 9所示。稍后会详细介绍await表达式,不过
本节你将对其位置和作用有个大致了解。这三个区域如下。
- 第一个awa表达式之前的部分:从方法开头到第一个await表达式之前的所有代码。这
一部分应该只包含少量无须长时间处理的代码。 - await表达式:表示将被异步执行的任务。
- 后续部分:await表达式之后的方法中的其余代码。包括其执行环境,如所在线程信息、
目前作用域内的变量值,以及当await表达式完成后重新执行时所需的其他信息。
async Task<int> CountCharacterAsync(int id,string site)
{Console.WriteLine("Starting CountCharacters");WebClient wc = new WebClient(); //第一个await表达式之前的部分string result = await wc.DownloadStringTaskAsync(new Uri(site));//await表达式Console.WriteLine("CountCharacter Completed");return result.Length;
}
图21-10阐明了一个异步方法的控制流。它从第一个await表达式之前的代码开始,正常
(同步地)执行直到遇见第一个await。这一区域实际上在第一个await表达式处结束,此时await
的任务还没有完成(大多数情况下如此)。当await的任务完成时,方法将继续同步执行。如果
还有其他await,就重复上述过程。
当到达await表达式时,异步方法将控制返回到调用方法。如果方法的返回类型为Task或
Task类型,则方法将创建一个Task对象,表示需异步完成的任务和后续,然后将该Task返回
到调用方法。
目前有两个控制流:一个在异步方法内,一在调用方法内。异步方法内的代码完成以下工作。
-
异步执行await表达式的空闲任务。
-
当await表达式完成时,执行后续部分。后续部分本身也可能包含其他await表达式,这
些表达式也将按照相同的方式处理,即异步执行表达式,然后执行后续部分。 -
当后续部分遇到return语句或到达方法末尾时,
- 如果方法的返回类型为void,控制流将退出。
- 如果方法的返回类型为Task,则后续部分设置Task的状态属性并退出。如果返回类型
为Task或ValueTask,则后续部分还将设置对象的Result属性。
同时,调用方法中的代码将继续其进程,从异步方法获取Task或ValueTask对象。当
需要实际值时,就引用Task或对象的Result属性。届时,如果异步方法设置了该属
性,调用方法就能获得该值并继续,否则它将暂停并等待该属性被设置,然后再继续执行。
很多人可能不解的一点是同步方法第一次遇到await时所返回对象的类型。这个返回类型就
是同步方法头中的返回类型,它与await表达式的返回值类型一点关系也没有。
例如在下面的代码中,await表达式返回一个stringo但在方法的执行过程中,当到达await
表达式时,异步方法返回到调用方法的是一个Task对象,这正是该方法的返回类型。
private async Task<int>CountCharacterAsync(string site)
{WebClient wc = new WebClient();string result = await wc.DownloadStringTaskAsync(new Uri(site));return result.Length;
}
await表达式
await表达式指定了一个异步执行的任务。其语法如下所示,由await关键字和一个空闲对
象(称为任务)组成。这个任务可能是一个Task类型的对象,也可能不是。默认情况下,这个
任务在当前线程上异步运行。
** await task**
一个空闲对象即是一个awaitable类型的实例。awaitable类型是指包含GetAwaiter方法的
类型,该方法没有参数,返回一个awaiter类型的对象。awaiter类型包含以下成员:
bool IsCompleted { get; }void OnCompleted(Action);
它还包含以下成员之一
void GetResult();T GetResult();(T为任意类型)
然而实际上,你并不需要构建自己的awaitable。相反,你应该使用Task或Va1ueTask类,
它们是itab类型。对于awaitable,大多数程序员所需要的就是它们了。
在.NET4.5中,微软发布了大量新的和修订的异步方法(在BCL中),它们可返回Task
类型的对象。将这些放到你的await表达式中,它们将在当前线程中异步执行。
在之前的很多示例中,我们都使用了WebClient.DownloadStringTaskAsync方法,它也是这些
异步方法中一个。以下代码阐明了其用法:
Uri site = new Uri("http://www.illustratedcsharp.com"); WebClient wc = new WebClient(); string result = await wc.DownloadStringTaskAsync(site);
尽管目前BCL中存在很多返回Task类型对象的方法,你仍然可能需要编写自己的方法,
作为await表达式的任务。最简单的方式是在你的方法中使用Task.Run方法来创建一个Task。
关于Task.Run,有一点非常重要,即它在不同的线程上运行你的方法。
Task.Run的一个签名如下,它以Func委托为参数。如第20章所述,Func
是一个预定义的委托,它不包含任何参数,返回值的类型为TReturn:
Task Run(Func<TReturn>func)
因此,要将你的方法传递给Task.Run方法,需要基于该方法创建一个委托。下面的代码展
示了三种实现方式。其中,Get10与Func委托兼容,因为它没有参数并且返回int。
- 第一个实例(DoWorkAsync方法的前两行)使用Get10创建名为ten的Func〈int〉委托。然
后在下一行将该委托用于Task.Run方法。 - 第二个实例在Task.Run方法的参数列表中创建Func委托。
- 第三个实例没有使用Get10方法,而是使用了组成Get10方法体的return语句,并将其
用于与Func委托兼容的Lambda表达式。该Lambda表达式将隐式转换为该委托。
class MyClass
{public int Get10() //与Func<int>兼容{return 10;}public async Task DoWorkAsync(){Func<int>ten=new Func<int>(Get10);int a=await Task.Run(ten);int b=await Task.Run(new Func<int>(Get10));int c=await Task.Run(()=>{return 10;});Console.WrtiteLine($"{a}{b}{c}");}class Program{static void Main(){Task t=(new MyClass()).DoWorkAsync();t.Wait();}}
}
在上面的示例代码中,我们使用的Task.Run的签名以Func为参数。该方法共有8
个重载,如表21-1所示。表21·2展示了可能用到的4个委托类型的签名。
下面的代码展示了4个await语句,它们使用Task.Run方法来运行4种不同的委托类型所
表示的方法:
static class MyClass
{public static async Task DoWorkAsync(){await Task.Run(()=>Console.WriteLine(5.ToString()));Console.WriteLine((await Task.Run(()=>6)).ToString());await Task.Run(()=>Task.Run(()=>Console.WriteLine(7.ToString())));int value=await Task.Run(()=>Task.Run(()=>8));Console.WriteLine(value.ToString());}
}class Program
{static void Main(){Task t=MyClass.DoWorkAsync();t.Wait();Console.WriteLine("Press Enter key to exit");Console.Read();}
}
在能使用任何其他表达式的地方,都可以使用await表达式(只要位于异步方法内)。在上
面的代码中,4个await表达式用在了3个不同的位置。
- 第一个和第三个实例将await表达式用作语句。
- 第二个实例将await表达式用作WriteLine方法的参数。
- 第四个实例将await表达式用作赋值语句的右端。
假设我们的某个方法不符合这4种委托形式。例如,假设有一个GetSum方法以两个int值
作为输入,并返回这两个值的和。这与上述4个可接受的委托都不兼容。要解决这个问题,可以
用可接受的Func委托的形式创建一个Lambda函数,其唯一的行为就是运行GetSum方法,如下
面的代码所示:
int value=await Task.Run(()=>GetSum(5,6));
Lambda函数()=>GetSum(5,6)满足FunC委托,因为它没有参数,且返回单一的
值。下面的代码展示了一个完整的示例:
static class MyClass
{private static int GetSum(int i1,int i2){return i1+i2;}public static async Task DoWorkAsync(){int value=await Task.Run(()=>GetSum(5,6));Console.WriteLine(value.ToString());}
}Class Program
{static void Main(){Task t=MyClass.DowWorkAsync();t.Wait();Console.WriteLine("Press Enter key to exit");Console.Read();}
}
取消一个异步操作
一些NET异步方法允许你请求终止执行。你也可以在自己的异步方法中加入这个特性。
System.Threading.Tasks命名空间中有两个类是为此目的而设计的:CancellationToken和
CancellationTokenSource。
-
CancellationToken对象包含一个任务是否应被取消的信息。
-
拥有CancellationToken对象的任务需要定期检查其令牌(token)状态。如果CancellationToken
对象的IsCancellationRequested属性为true,任务需停止其操作并返回。 -
CancellationToken是不可逆的,并且只能,更用一次。也就是说,一旦IsCancellationRequested
属性被设置为true,就不能更改了。 -
CancellationTokenSource对象创建可分配给不同任务的CancellationToken对象。任何持
有CancellationTokenSource的对象都可以调用其Cancel方法,这会将CanceIIationToken
的IsCancellationRequested属性设置为true。下面的代码展示了如何使用cancellationTokensource和CancellationToken来实现取消操作。
注意:该过程是协同的,即调用CancellationTokenSource的Cancel时,它本身并不会执行取消操作,
而是会将CancellationToken的IsCancellationRequested属性设置为true。包含CancellationToken
的代码负责检查该属性,并判断是否需要停止执行并返回。
下面的代码展示了如何使用这两个取消类。如下所示代码并没有取消异步方法,而是在Main
方法中间包含两行被注释的代码,它们触发了取消行为。
class Program
{static void Main(){CancellationTokenSource cts=new CancellationTokenSource();CancellationToken token=cts.Token;MyClass mc=new MyClass();Task t=mc.RunAsync(token);//Thread.Sleep(3000);//cts.Cancel();t.Wait();Console.WriteLine($"Was Cancelled:{token.IsCancellationRequested}");}
}class MyClass
{public async Task RunAsync(CancellationToken ct){if(ct.IsCancellationRequested)return;await Task.Run(()=>CycleMethod(ct),ct);}void CycleMethod(CancellationToken ct){Console.WriteLine("Starting CycleMethod");const int max=5;for(int i=0;i<max;i++){if(ct.IsCancellationRequested)return;Thread.Sleep(1000);Console.WriteLine($" {i+1} 0f {max} iterations completed");}}
}
异常处理和await表达式
可以像使用其他表达式那样,将await表达式放在try语句内,try.·.catch··.finally结构
将按你期望的那样工作。
下面的代码展示了一个示例,其中await表达式中的任务会抛出一个异常。await表达式位
于try块中,将按普通的方式处理异常。
class Program
{static void Main(string[] args){Task t=BadAsync();t.Wait();Console.WriteLine($"Task Status:{t.Status}");Console.WriteLine($"Task IsFaulted:{t.IsFaulted}");}static async Task BadAsync(){try{await Task.Run(()=>{throw new Exception();})}catch{Console.WriteLine("Exception in BadAsync");}}
}
注意,尽管Task抛出了一个Exception,但在Main的最后,Task的状态仍然为RanToCompletion。
这会让人感到很意外,因为异步方法抛出了异常。原因是以下两个条件成立:(1)Task没有被取
消,(2)没有未处理的异常。类似地,IsFaulted属性为False,因为没有未处理的异常。
从C#6.0开始,也可以在catch和finally块中使用await表达式了。在初始异常不需要终
止应用程序的时候,可以使用await来执行日志记录或其他运行时间较长的任务。如果错误严重
到阻止应用程序继续运行,那么以异步的方式执行catch或finally任务几乎没有什么好处。
然而,如果新的异步任务也产生了异常,那么任何原有的异常信息都将丢失,从而使调试原
始错误变得更加困难。
在调用方法中同步地等待任务
调用方法可以调用任意多个异步方法并接收它们返回的Task对象。然后你的代码会继续执
行其他任务,但在某个点上可能会需要等待某个特殊Task对象完成,然后再继续。为此,Task
类提供了一个实例方法Wait,可以在Task对象上调用该方法。
下面的示例展示了其用法。在代码中,调用方法DoRun调用异步方法CountCharactersAsync
并接收其返回的Task0然后调用Task实例的wait方法,等待任务Task结束。等结束时再
显示结果信息。
static class MyDownloadString
{public static void DoRun(){Task<int> t=CountCharactersAsync("http://www.illustratedcsharp.com");t.Wait();Console.WriteLine($"The task has finished,returning value{t.Result}");}private static async Task<int> CountCharactersAsync(string site){string result=await new WebClient().MyDownloadStringTaskAsync(new Uri(site));return result.Length;}
}class Program
{static void Main(){MyDownloadString.DoRun();}
}
方法用于单一Task对象。但你也可以等待一组Task对象。对于一组Task,可以等待所
有任务都结束,也可以等待某一个任务结束。实现这两个功能的是Task类中的两个静态方法:
- WaitAll
- WaitAny
这两个方法是同步方法且没有返回值。也就是说,它们停止,直到条件满足后再继续执行。
我们来看一个简单的程序,它包含一个方法,该方法两次调用一个异步方法并获取其
返回的两个Task对象。然后,方法继续执行,检查任务是否完成并打印。方法最后会等待
调用Console.Read,该方法等待并接收键盘输人的字符。把它放在这里是因为不然的话main方
法会在异步任务完成前退出。
如下所示的程序并没有使用等待方法,而是在方法中间注释的部分包含等待的代码,
我们将在稍后用它来与现在的版本进行比较。
class MyDownloadString
{Stopwatch sw=new Stopwatch();public void DoRun(){sw.Start();Task<int> t1=CountCharactersAsync(1,"http://www.microsoft.com");Task<int> t2=CountCharactersAsync(2,"http://www.illustratedcsharp.com");//Task.WaitAll(t1,t2);//Task.WaitAny(t1,t2);Console.WriteLine("Task 1:{0}Finished",t1.IsCompleted?"":"Not ");Console.WriteLine("Task 2:{0}Finished",t2.IsCompleted?"":"Not");Console.Read();}private async Task<int>CountCharactersAsync(int id,string site){WebClient wc=new WebClient();string result=await wc.MyDownloadStringTaskAsync(new Uri(site));Console.WriteLine(" Call{0}completed:{1,4:No}ms",id,sw.Elapsed.TotalMilliseconds);return result.Length;}
}class Program
{static void Main(){MyDownloadString ds=new MyDownloadString();ds.DoRun();}
}
签 名 | 描 述 |
---|---|
void WaitAll(params Task[] tasks) | 等待所有任务完成 |
bool WaitAll(Task[] tasks, int millisecondsTimeout) | 等待所有任务完成。如果在超时时限内没有全部完成,则返回 false 并继续执行 |
void WaitAll(Task[] tasks, CancellationToken token) | 等待所有任务完成,或等待 CancellationToken 发出取消信号 |
bool WaitAll(Task[] tasks, TimeSpan span) | 等待所有任务完成。如果在超时时限内没有全部完成,则返回 false 并继续执行 |
bool WaitAll(Task[] tasks, int millisecondsTimeout, CancellationToken token) | 等待所有任务完成,或等待 CancellationToken 发出取消信号。如果在超时时限内没有发生上述情况,则返回 false 并继续执行 |
void WaitAny(params Task[] tasks) | 等待任意一个任务完成 |
bool WaitAny(Task[] tasks, int millisecondsTimeout) | 等待任意一个任务完成。如果在超时时限内没有完成的,则返回 false 并继续执行 |
void WaitAny(Task[] tasks, CancellationToken token) | 等待任意一个任务完成,或等待 CancellationToken 发出取消信号 |
bool WaitAny(Task[] tasks, TimeSpan span) | 等待任意一个任务完成。如果在超时时限内没有完成的,则返回 false 并继续执行 |
bool WaitAny(Task[] tasks, int millisecondsTimeout, CancellationToken token) | 等待任意一个任务完成,或等待 CancellationToken 发出取消信号。如果在超时时限内没有发生上述情况,则返回 false 并继续执行 |
在异步方法中异步地等待任务
上一节学习了如何同步地等待Task完成。但有时在异步方法中,你会希望用await表达式
来等待Task。这时异步方法会返回到调用方法,但该异步方法会等待一个或所有任务完成。可以
通过Task.WhenAll和Task.WhenAny方法来实现。这两个方法称为组合子(combinator)。
下面的代码展示了一个使用Task.WhenAll方法的示例。该方法异步地等待所有与之相关的
Task完成,不会占用主线程的时间。注意,await表达式的任务就是调用Task.WhenAll。
using System;
using Systme.Collections.Generic;
using System.Net;
using System.Threading.Tasks;class MyDownloadString
{public void DoRun(){Task<int> t=CountCharacterAsync("http://www.microsoft.com","http://www.illustratedcsharp.com");Console.WriteLine("DoRun: Task{0}Finished",t.IsCompleted?"":"Not");Console.WriteLine("DoRun: Result={0}",t.Result);}private async Task<int>CountCharactersAsync(string site1,string site2){WebClient Wc1=new WebClient();WebClient Wc2=new WebClient();Task<string>t1=Wc1.MyDownloadStringTaskAsync(new Uri(site1));Task<string>t2=Wc1.MyDownloadStringTaskAsync(new Uri(site2));List<Task<string>>tasks=new List<Task<string>>();tasks.Add(t1);tasks.Add(t2);await Task.WhenAll(tasks);Console.WriteLine(" CCA:T1 {0}Finised",t1.IsCompleted?" ":"Not");Console.WriteLine(" CCA:T2{0}Finised",t2.IsCompleted?"":"Not");return t1.IsCompleted?t1.Result.Length:t2.Result.Length;}
}class Program
{static void Main(){MyDownloadString ds=new MyDownloadString();ds.DoRun();}
}
Task·Delay方法
Task.Delay方法创建一个Task对象,该对象将暂停其在线程中的处理,并在一定时间之后
完成。然而与Thread.Sleep阻塞线程不同的是,Task.Delay不会阻塞线程,线程可以继续处理
其他工作。
下面的代码展示了如何使用Task.Delay方法:
class Simple
{Stopwatch sw=new Stopwatch();public viod DoRun(){Console.WriteLine("Caller:Before call");ShowDelayAsync();Console.WriteLine("Caller:After call");}private async void ShowDelayAsync(){sw.Start();Console.WriteLine($" Before Delay:{sw.ElapsedMilliseconds}");await Task.Delay(1000);Console.WriteLine($" After Delay:{sw.ElapsedMilliseconds}");}
}class Program
{static void Main(){Simple ds=new Simple();ds.DoRun();Console.Read();}
}
签名 | 描述 |
---|---|
Task Delay(int millisecondsDelay) | 在以毫秒表示的延迟时间到期后,返回完成的 Task 对象 |
Task Delay(TimeSpan delay) | 在以.NET TimeSpan 对象表示的延迟时间到期后,返回完成的 Task 对象 |
Task Delay(int millisecondsDelay, CancellationToken token) | 在以毫秒表示的延迟时间到期后,返回完成的 Task 对象。可通过取消令牌来取消该操作 |
Task Delay(TimeSpan delay, CancellationToken token) | 在以.NET TimeSpan 对象表示的延迟时间到期后,返回完成的 Task 对象。可通过取消令牌来取消该操作 |