当前位置: 首页 > news >正文

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个委托类型的签名。

Task.Run重载的返回类型和签名

可作为Task.Run方法第一个参数的委托类型

下面的代码展示了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 对象。可通过取消令牌来取消该操作
http://www.lryc.cn/news/612791.html

相关文章:

  • PyTorch 核心三件套:Tensor、Module、Autograd
  • `/dev/vdb` 是一个新挂载的 4TB 硬盘,但目前尚未对其进行分区和格式化。
  • vscode 打开设置
  • Flutter 三棵树
  • 【物联网】基于树莓派的物联网开发【25】——树莓派安装Grafana与Influxdb无缝集成
  • CentOS 7 下通过 Anaconda3 运行llm大模型、deepseek大模型的完整指南
  • 人工智能的20大应用
  • 从Centos 9 Stream 版本切换到 Rocky Linux 9
  • 360纳米AI、实在Agent、CrewAI与AutoGen……浅析多智能体协作系统
  • 构建在 OpenTelemetry eBPF 基础之上:详解 Grafana Beyla 2.5 新特性
  • 【0基础3ds Max】菜单栏介绍
  • 多模态融合(Multimodal Fusion)
  • PCIe Base Specification解析(九)
  • mapbox进阶,mapbox-gl-draw绘图插件扩展,绘制新增、编辑模式支持点、线、面的捕捉
  • 什么是SpringBoot
  • Shuffle SOAR使用学习经验
  • Q-Learning详解:从理论到实践的全面解析
  • 扎根国际数字影像产业园:共享空间助力企业高效发展
  • 施耐德 Easy Altivar ATV310 变频器:高效电机控制的理想选择(含快速调试步骤及常见故障代码)
  • 【3D图像技术分析与实现】谷歌的AlphaEarth是如何实现的?
  • 告别Cursor!最强AI编程辅助Claude Code安装到使用全流程讲解
  • 常见命令-资源查看-iostat命令实践
  • cuda编程笔记(13)--使用CUB库实现基本功能
  • 基于LLM的大数据分析调研
  • 大模型量化原理解析
  • 支持DeepSeek_Qwen等大模型!字狐Chatbox在线模型+本地部署模型
  • 如何封锁品类?提升垂类竞争力
  • leetcode 674.最长连续递增序列
  • 菜鸟笔记007 [...c(e), ...d(i)]数组的新用法
  • 解决 npm i sharp@0.23.4 安装失败异常 npm install sharp异常解决