当前位置:   article > 正文

C#如何使用Task完成异步方法_c# task异步任务

c# task异步任务

目录

一、async和await特性的结构

1. 异步和同步

2.async和await

二、什么是异步方法

1.异步方法的结构

2.异步方法三种返回类型理解

3.异步方法的控制流

三、await表达式

四、取消一个异步操作

五、异常处理的await表达式

六、在调用方法中同步地等待任务

1. Wait

2. WaitAll和WaitAny

七、在异步方法中异步地等待任务 

八、Task.Delay方法


一、async和await特性的结构

1. 异步和同步

  1. 同步方法:如果一个方法被调用了,等待其执行所有处理后调用方法才继续执行的方法。
  2. 异步方法:异步方法能在处理完成之前就回到调用方法。

2.async和await

  1. 调用方法:该方法调用异步方法,然后在异步方法(可能在相同的线程,也可能在不同的线程)执行其任务时继续执行。
  2. 异步(async)方法:该方法异步执行其工作,被调用时立即回到调用方法。
  3. await表达式:用于异步方法内部,指明需要异步执行的任务。一个异步方法需要至少包含一个await表达式。

二、什么是异步方法

1.异步方法的结构

异步方法在完成其工作之前即返回到调用方法,然后在调用方法继续执行的时候完成其工作。

  1. 方法头包含async方法修饰符,该修饰符仅标识此方法是一个异步方法。
  2. 包含至少一个await表达式,表示可以异步完成的任务。
  3. 必须具备以下三种返回类型中的一种:void、Task、Task<T>。
  4. 异步方法的参数可以是任意类型,但不能为ref或out。
  5. 按照约定,异步方法的名称应该为Async为后缀。
  6. 除了方法以外,Lambda表达式和匿名方法也可以作为异步对象。

2.异步方法三种返回类型理解

1. Task<T> :如果调用方法要从调用中获取一个T类型的值,异步方法的返回类型就必须是Task<T>。调用方法通过读取Task的Result属性来获取这个T类型的值。

  1. // 使用返回Task<int>对象的异步方法代码示例:
  2. static class DoAsyncStuff
  3. {
  4. public static async Task<int> CalculateSumAsync(int i1, int i2)
  5. {
  6. int sum = await Task.Run(() => GetSum(i1, i2));
  7. return sum;//注意返回的是一个int类型
  8. }
  9. private static int GetSum(int i1, int i2) { return i1 + i2; }
  10. }
  11. internal class Program
  12. {
  13. static void Main(string[] args)
  14. {
  15. Task<int> task = DoAsyncStuff.CalculateSumAsync(1, 2);
  16. //处理其他事情...
  17. Console.WriteLine("Value:{0}",task.Result);//调用方法通过Result获取这个int类型的值
  18. }
  19. }

2.Task : 如果调用方法不需要从异步方法中返回某个值,但需要检查异步方法的状态,那么异步方法可以返回一个Task类型的对象,这时即使异步方法中出现了return语句,也不会返回任何东西。

  1. //使用Task不返回类型的异步方法代码示例:
  2. static class DoAsyncStuff
  3. {
  4. public static async Task CalculateSumAsync(int i1, int i2)
  5. {
  6. int sum = await Task.Run(() => GetSum(i1, i2));
  7. Console.WriteLine("Value:{0}",sum);
  8. }
  9. private static int GetSum(int i1, int i2) { return i1 + i2; }
  10. }
  11. internal class Program
  12. {
  13. static void Main(string[] args)
  14. {
  15. Task task = DoAsyncStuff.CalculateSumAsync(1, 2);
  16. //处理其他事情...
  17. task.Wait();
  18. Console.WriteLine("异步方法结束");
  19. }
  20. }

3. void :如果调用方法仅仅想执行异步方法,而不需要与它进行进一步交互时【称为“调用并忘记”】,异步方法可以返回void类型。

  1. //使用“调用并忘记”的异步方法的代码示例:
  2. static class DoAsyncStuff
  3. {
  4. public static async void CalculateSumAsync(int i1, int i2)
  5. {
  6. int sum = await Task.Run(() => GetSum(i1, i2));
  7. Console.WriteLine("Value:{0}",sum);
  8. }
  9. private static int GetSum(int i1, int i2) { return i1 + i2; }
  10. }
  11. internal class Program
  12. {
  13. static void Main(string[] args)
  14. {
  15. DoAsyncStuff.CalculateSumAsync(1, 2);
  16. //处理其他事情...
  17. Thread.Sleep(200);
  18. Console.WriteLine("Program Exiting");
  19. }
  20. }

3.异步方法的控制流

1. 异步方法的结构包含三个不同的区域:

 控制流阐述:

  1. 调用异步方法后,知道遇到第一个await表达式之前都是同步的。
  2. 遇到await表达式之后,异步方法将控制返回到调用方法。如果方法的返回类型为Task或Task<T>类型,将创建一个Task对象,表示需异步完成的任务和后续,然后将Task返回到调用方法。
  3. 到目前为止,已经出现两个控制流,异步方法内的和调用方法内的。异步方法的代码完成以下工作:
    1. 异步执行await表达式的空闲任务。
    2. 当await表达式完成时,执行后续部分。如果后续还有await表达式,将重复此步骤:异步执行await表达式,然后执行后续部分。
  4. 当后续部分遇到return语句或到达方法末尾时:
    1. 如果方法返回类型为void,控制流将退出。
    2. 如果方法返回类型为Task,后续部分设置Task的属性并退出。
    3. 如果方法返回类型为Task<T>,后续部分还将设置Task的Result属性。
  5. 同时调用方法中的代码继续其进程,从异步方法获取Task对象。当需要其实际值时,就引用Task对象的Result属性,届时,如果异步方法设置了该属性,调用方法就能调用其值并继续,否则将暂停等待该属性被设置,然后再继续执行。

 需要注意的是:异步方法的return语句并没有真正返回一个值,它只是退出了,异步方法中的返回类型始终是方法头中声明的返回类型。

 

三、await表达式

await表达式指定了一个异步执行的任务。这个任务可能是一个Task类型的对象,也可能不是,默认情况下这个任务在当前线程异步运行。

我们可能需要编写自己的方法作为await表达式的任务。最简单的方式是在你的方法中使用Task.Run创建一个Task。(关于Task.Run即在不同的线程上运行你的方法)Task.Run方法有8个重载 ,如下图所示:

以Func<int> 委托作为参数的代码示例:

  1. class MyClass
  2. {
  3. public int Get10()
  4. {
  5. return 10;
  6. }
  7. public async Task DoWorkAsync()
  8. {
  9. //方式1:使用Get10创建名为ten的Func<int>委托,将该委托传入Task.Run方法
  10. Func<int> ten = new Func<int>(Get10);
  11. int a = await Task.Run(ten);
  12. //方式2:直接在Task.Run中创建委托,传入Get10方法
  13. int b = await Task.Run(new Func<int>(Get10));
  14. //方式3:使用与Func<T>兼容的Lambda表达式,Lambda表达式将隐式转换为该委托
  15. int c = await Task.Run(() => { return 10; });
  16. Console.WriteLine("{0},{1},{2}",a,b,c);
  17. }
  18. }
  19. internal class Program
  20. {
  21. static void Main(string[] args)
  22. {
  23. Task task = new MyClass().DoWorkAsync();
  24. task.Wait();
  25. }
  26. }

从Task.Run的重载中可以发现,可以作为Run中第一个参数的委托有四种:

使用四种委托作为参数的Run方法代码示例:

  1. class MyClass
  2. {
  3. public static async Task DoWorkAsync()
  4. {
  5. await Task.Run(() => Console.WriteLine(5.ToString()));//Aciton,无参无返
  6. Console.WriteLine((await Task.Run(() => 6)).ToString());//TResult Func(),无参有返,返回TResult类型对象
  7. await Task.Run(() => Task.Run(() => Console.WriteLine(7.ToString())));//Task Func(),无参有返,返回简单Task对象
  8. Console.WriteLine((await Task.Run(() => Task.Run(() => 8))).ToString());//Task<TResult> Func(),无参有返,返回Task<T>对象
  9. }
  10. }
  11. internal class Program
  12. {
  13. static void Main(string[] args)
  14. {
  15. Task task = MyClass.DoWorkAsync();
  16. task.Wait();
  17. }
  18. }

假如我们需要一个接受参数的方法,但是无法匹配上述的四种委托(接受没有参数的方法的委托),我们可以创建一个Lambda表达式,其唯一行为就是运行我们的接受参数的方法,来满足Run方法所能接受的委托形式。代码示例如下:

  1. class MyClass
  2. {
  3. //需要使用异步的方法
  4. public static int GetSum(int a, int b) { return a + b; }
  5. public static async Task DoWorkAsync(int a,int b)
  6. {
  7. int result = await Task.Run(() => GetSum(a, b));//创建一个满足Func<TResult>委托形式的Lambda表达式
  8. Console.WriteLine(result);
  9. }
  10. }
  11. internal class Program
  12. {
  13. static void Main(string[] args)
  14. {
  15. Task task = MyClass.DoWorkAsync(6,7);
  16. task.Wait();
  17. }
  18. }

四、取消一个异步操作

一些.NET异步方法允许你请求终止执行,我们可以在自己的异步方法中加入这个特性。有两个类:CancellationToken和CancellationTokenSource是专门为了取消异步操作设计的。

1. CancellationToken对象包含了一个任务是否应被取消的消息。拥有CancellationToken对象的任务需要定期检查其令牌(token)的状态,如果CancellationToken对象中的IsCancellationRequested属性为true,任务需停止其操作并返回。CancellationToken是不可逆的,也就是一旦IsCancellationRequested设置为true就不能更改了。

2.  CancellationTokenSource对象创建可分配给不同任务的CancellationToken对象。任何持有CancellationTokenSource的对象都可以调用其Cancel方法,这会将CancellationToken对象中的IsCancellationRequested设置为true。

3. 使用这两个取消类的代码:

  1. class MyClass
  2. {
  3. public async Task RunAsync(CancellationToken ct)
  4. {
  5. if (ct.IsCancellationRequested)
  6. return;
  7. await Task.Run(()=>CycleMethod(ct));
  8. }
  9. void CycleMethod(CancellationToken ct)
  10. {
  11. Console.WriteLine("开始CycleMethod方法");
  12. const int max = 5;
  13. for (int i = 0; i < max; i++)
  14. {
  15. if (ct.IsCancellationRequested)
  16. return;
  17. Thread.Sleep(1000);
  18. Console.WriteLine("完成:{0}/{1}",i+1,max);
  19. }
  20. }
  21. }
  22. internal class Program
  23. {
  24. static void Main(string[] args)
  25. {
  26. CancellationTokenSource cts = new CancellationTokenSource();
  27. CancellationToken ct = cts.Token;
  28. MyClass mc = new MyClass();
  29. Task t = mc.RunAsync(ct);
  30. /*
  31. 取消异步操作的过程是协同的:
  32. 即调用CancellationTokenSource的Cancel时,它本身并不会执行取消操作
  33. 而是会将CancellationToken的IsCancellationRequested属性设置为true
  34. 包含CancellationToken的代码负责检查该属性,并判断是否需要停止执行并返回
  35. */
  36. //Thread.Sleep(3000);
  37. //cts.Cancel();//解除注释将产生取消异步的操作
  38. t.Wait();
  39. Console.WriteLine("是否取消了异步方法:{0}", ct.IsCancellationRequested);
  40. }
  41. }

五、异常处理的await表达式

就使用try...catch...正常处理异常就行。

  1. class MyClass
  2. {
  3. public static async Task BadAsync()
  4. {
  5. try
  6. {
  7. await Task.Run(() => { throw new Exception(); });
  8. }catch (Exception ex)
  9. {
  10. Console.WriteLine("异步中抛出异常...");
  11. }
  12. }
  13. }
  14. internal class Program
  15. {
  16. static void Main(string[] args)
  17. {
  18. Task task = MyClass.BadAsync();
  19. task.Wait();
  20. Console.WriteLine("Task Status:{0}",task.Status);//Task Status:RanToCompletion,因为Task没有被取消
  21. Console.WriteLine("Task IsFaulted:{0}",task.IsFaulted);//Task IsFaulted:False,因为没有未处理的异常
  22. }
  23. }

六、在调用方法中同步地等待任务

1. Wait

Task的Wait方法可以让调用方法等待异步方法完成,在Wait方法处,流程是阻塞的。

  1. class MyClass
  2. {
  3. public void PrintA()
  4. {
  5. const int max = 10;
  6. for (int i = 0; i < max; i++)
  7. {
  8. Thread.Sleep(1000);
  9. Console.WriteLine("A任务完成:{0}/{1}", i + 1, max);
  10. }
  11. }
  12. public async Task ForWaitAsync()
  13. {
  14. await Task.Run(new Action(PrintA));
  15. }
  16. }
  17. internal class Program
  18. {
  19. static void Main(string[] args)
  20. {
  21. MyClass mc = new MyClass();
  22. Task t = mc.ForWaitAsync();
  23. Thread.Sleep(6000);//主线程休眠6秒
  24. //等待任务完成,使用此方法可以让主方法等待异步方法完成,否则主方法休眠六秒结束时异步方法也将直接结束
  25. t.Wait();
  26. }
  27. }

2. WaitAll和WaitAny

Wait是等待单一任务完成,我们也可以使用WaitAll和WaitAny等待多个任务。(这两个方法是同步方法,知道等待条件满足后再继续执行,不然流程会阻塞在那里)

  1. class MyClass
  2. {
  3. public void PrintA()
  4. {
  5. const int max = 10;
  6. for (int i = 0; i < max; i++)
  7. {
  8. Thread.Sleep(1000);
  9. Console.WriteLine("A任务完成:{0}/{1}", i + 1, max);
  10. }
  11. Console.WriteLine("A任务全部完成!");
  12. }
  13. public void PrintB()
  14. {
  15. const int max = 5;
  16. for (int i = 0; i < max; i++)
  17. {
  18. Thread.Sleep(1000);
  19. Console.WriteLine("B任务完成:{0}/{1}", i + 1, max);
  20. }
  21. Console.WriteLine("B任务全部完成!");
  22. }
  23. public async Task AForWaitAsync()
  24. {
  25. await Task.Run(new Action(PrintA));
  26. }
  27. public async Task BForWaitAsync()
  28. {
  29. await Task.Run(new Action(PrintB));
  30. }
  31. }
  32. internal class Program
  33. {
  34. static void Main(string[] args)
  35. {
  36. MyClass mc = new MyClass();
  37. Task a = mc.AForWaitAsync();
  38. Task b = mc.BForWaitAsync();
  39. Task[] task = new Task[] { a, b };//存放Task的数组
  40. Thread.Sleep(8000);//调用方法休眠8秒
  41. //Task.WaitAll(task);//等待所有任务完成
  42. Task.WaitAny(task);//等待任意一个任务完成后将不再阻塞
  43. }
  44. }

WaitAll和WaitAny分别还包含四个重载:

七、在异步方法中异步地等待任务 

有时在异步方法中,你会希望用await表达式来等待Task。这时异步方法会返回到调用方法,但该异步方法会等待一个或所有任务完成。可以通过Task.WhenAll或Task.WhenAny方法来实现。这两个方法称为组合子。

  1. class MyClass
  2. {
  3. public void PrintA()
  4. {
  5. const int max = 10;
  6. for (int i = 0; i < max; i++)
  7. {
  8. Thread.Sleep(1000);
  9. Console.WriteLine("A任务完成:{0}/{1}", i + 1, max);
  10. }
  11. }
  12. public void PrintB()
  13. {
  14. const int max = 5;
  15. for (int i = 0; i < max; i++)
  16. {
  17. Thread.Sleep(1000);
  18. Console.WriteLine("B任务完成:{0}/{1}", i + 1, max);
  19. }
  20. }
  21. public async Task AAsync()
  22. {
  23. await Task.Run(new Action(PrintA));
  24. }
  25. public async Task BAsync()
  26. {
  27. await Task.Run(new Action(PrintB));
  28. }
  29. public async Task ForWhenAsync()
  30. {
  31. Task a = AAsync();
  32. Task b = BAsync();
  33. Task[] tasks = { a, b };//Task数组
  34. //await Task.WhenAll(tasks);//在异步中等待所有任务
  35. await Task.WhenAny(tasks);//在异步中等待任意一个任务
  36. //此异步方法中的流程会阻塞在Task.WhenAny(tasks)中,直到某个条件满足才会到达这里
  37. Console.WriteLine("现在任务A是否完成:{0}",a.IsCompleted?"Yes":"No");
  38. Console.WriteLine("现在任务B是否完成:{0}",b.IsCompleted?"Yes":"No");
  39. }
  40. }
  41. internal class Program
  42. {
  43. static void Main(string[] args)
  44. {
  45. const int max = 12;
  46. MyClass mc = new MyClass();
  47. mc.ForWhenAsync();
  48. for (int i = 0;i < max ; i++)
  49. {
  50. Thread.Sleep(1000);
  51. Console.WriteLine("Main任务完成:{0}/{1}", i + 1, max);
  52. }
  53. }
  54. }

八、Task.Delay方法

Task.Delay方法创建一个对象,该对象将暂停其在线程中的处理,并在一定时间之后完成。和Thread.Sleep阻塞线程不同的是,Task.Delay方法不会阻塞线程,线程可以继续处理其他工作。

  1. class Simple
  2. {
  3. Stopwatch sw = new Stopwatch();
  4. public void DoRun()
  5. {
  6. Console.WriteLine("调用之前");
  7. ShowDelayAsync();
  8. Console.WriteLine("调用之后");
  9. }
  10. private async void ShowDelayAsync()
  11. {
  12. sw.Start();
  13. Console.WriteLine("Delay 执行之前:{0}",sw.ElapsedMilliseconds);
  14. await Task.Delay(1000);//创建了一个Task对象,该对象将暂停其在线程中的处理,所以在打印下一句话之前,控制回到了调用方法打印调用方法中的"调用之后"
  15. Console.WriteLine("Delay 执行之后:{0}", sw.ElapsedMilliseconds);
  16. }
  17. }
  18. internal class Program
  19. {
  20. static void Main(string[] args)
  21. {
  22. Simple simple = new Simple();
  23. simple.DoRun();
  24. Console.Read();
  25. }
  26. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/104498
推荐阅读
相关标签
  

闽ICP备14008679号