【问题标题】:Understanding async / await in C#了解 C# 中的 async / await
【发布时间】:2012-12-20 02:47:32
【问题描述】:

我开始学习 C# 5.0 中的 async/await,但我完全不明白。我不明白它如何用于并行性。我尝试了以下非常基本的程序:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Task task1 = Task1();
            Task task2 = Task2();

            Task.WaitAll(task1, task2);

            Debug.WriteLine("Finished main method");
        }

        public static async Task Task1()
        {
            await new Task(() => Thread.Sleep(TimeSpan.FromSeconds(5)));
            Debug.WriteLine("Finished Task1");
        }

        public static async Task Task2()
        {
            await new Task(() => Thread.Sleep(TimeSpan.FromSeconds(10)));
            Debug.WriteLine("Finished Task2");
        }

    }
}

这个程序只是阻塞了对Task.WaitAll() 的调用并且永远不会结束,但我不明白为什么。我确定我只是遗漏了一些简单的东西,或者只是没有正确的心理模型,而且那里的博客或 MSDN 文章都没有帮助。

【问题讨论】:

标签: c# async-await


【解决方案1】:

我建议您从我的intro to async/await 开始,然后使用official MSDN documentation on TAP 跟进。

正如我在介绍性博文中提到的,有几个 Task 成员是 TPL 的保留者,在纯 async 代码中没有用处。 new TaskTask.Start 应替换为 Task.Run(或 TaskFactory.StartNew)。同样,Thread.Sleep 应替换为 Task.Delay

最后,我建议你不要使用Task.WaitAll;您的控制台应用程序应该只是 Wait 在使用 Task.WhenAll 的单个 Task 上。通过所有这些更改,您的代码将如下所示:

class Program
{
    static void Main(string[] args)
    {
        MainAsync().Wait();
    }

    public static async Task MainAsync()
    {
        Task task1 = Task1();
        Task task2 = Task2();

        await Task.WhenAll(task1, task2);

        Debug.WriteLine("Finished main method");
    }

    public static async Task Task1()
    {
        await Task.Delay(5000);
        Debug.WriteLine("Finished Task1");
    }

    public static async Task Task2()
    {
        await Task.Delay(10000);
        Debug.WriteLine("Finished Task2");
    }
}

【讨论】:

  • 感谢斯蒂芬的回复。你能给我 TL;DR 为什么 await Task.WhenAll(...) 比 Task.WaitAll() 更可取?此外,您能否向我提供任何文档的链接,这些文档解释了 WPF 和 Windows 运行时等 API 如何知道如何异步执行事件处理程序?
  • Blocking on tasks can lead to deadlock(链接到我的博客)。在您的特定情况下,它会很好,因为 Console Main 方法是“不阻止”指南的一个例外。但我更喜欢将异常代码(使用Wait)从任何逻辑中分离出来,我将其移至MainAsync。如果/当您将此代码复制/粘贴到 GUI 或 ASP.NET 应用程序中时,发生死锁的可能性要小得多。
  • 异步事件处理程序使用当前的SynchronizationContext。 WPF(我相信 WinRT)不必做任何特别的事情;他们现有的SynchronizationContext 足以让async 事件处理程序正常运行。我在我的介绍文章中解释了上下文捕获/恢复,如果你觉得有趣的话,我还有 an MSDN article 详细说明 SynchronizationContext
【解决方案2】:

了解 C# 任务、异步和等待

C# 任务

Task 类是一个异步任务包装器。 Thread.Sleep(1000) 可以停止线程运行 1 秒。虽然 Task.Delay(1000) 不会停止当前的工作。见代码:

public static void Main(string[] args){
    TaskTest();
}
private static void TaskTest(){
     Task.Delay(5000);
     System.Console.WriteLine("task done");
}

运行时,“任务完成”会立即显示。所以我可以假设 Task 中的每个方法都应该是异步的。如果我将 TaskTest () 替换为 Task.Run(() =>TaskTest()) 任务完成将不会显示,直到我附加一个 Console.ReadLine();在 Run 方法之后。

在内部,Task 类表示状态机中的线程状态。状态机中的每个状态都有多个状态,例如开始、延迟、取消和停止。

异步和等待

现在,您可能想知道如果所有 Task 都是异步的,那么 Task.Delay 的目的是什么?接下来,让我们通过使用 async 和 await 来真正延迟正在运行的线程

public static void Main(string[] args){
     TaskTest();
     System.Console.WriteLine("main thread is not blocked");
     Console.ReadLine();
}
private static async void TaskTest(){
     await Task.Delay(5000);
     System.Console.WriteLine("task done");
}

async 告诉调用者,我是一个异步方法,不要等我。 TaskTest() 中的 await 请求等待异步任务。现在,运行后,程序将等待 5 秒以显示任务完成文本。

取消任务

由于 Task 是一个状态机,所以必须有一种方法可以在任务运行时取消该任务。

static CancellationTokenSource tokenSource = new CancellationTokenSource();
public static void Main(string[] args){
    TaskTest();
    System.Console.WriteLine("main thread is not blocked");
    var input=Console.ReadLine();
    if(input=="stop"){
          tokenSource.Cancel();
          System.Console.WriteLine("task stopped");
     }
     Console.ReadLine();
}
private static async void TaskTest(){
     try{
          await Task.Delay(5000,tokenSource.Token);
     }catch(TaskCanceledException e){
          //cancel task will throw out a exception, just catch it, do nothing.
     }
     System.Console.WriteLine("task done");
}

现在,当程序运行时,可以输入“stop”来取消Delay任务。

【讨论】:

    【解决方案3】:

    您的任务永远不会完成,因为它们永远不会开始运行。

    我会Task.Factory.StartNew 创建一个任务并启动它。

    public static async Task Task1()
    {
      await Task.Factory.StartNew(() => Thread.Sleep(TimeSpan.FromSeconds(5)));
      Debug.WriteLine("Finished Task1");
    }
    
    public static async Task Task2()
    {
      await Task.Factory.StartNew(() => Thread.Sleep(TimeSpan.FromSeconds(10)));
      Debug.WriteLine("Finished Task2");
    }
    

    附带说明,如果您真的只是想暂停异步方法,则无需阻塞整个线程,只需使用 Task.Delay

    public static async Task Task1()
    {
      await Task.Delay(TimeSpan.FromSeconds(5));
      Debug.WriteLine("Finished Task1");
    }
    
    public static async Task Task2()
    {
      await Task.Delay(TimeSpan.FromSeconds(10));
      Debug.WriteLine("Finished Task2");
    }
    

    【讨论】:

    • 为什么要创建新任务?
    • @DavidHeffernan 还没有机会将其添加到我的答案中,但我认为 Alex 可能有更复杂的想法。
    • @MerickOWA 是的,你是对的,我确实有更复杂的想法。我想模拟阻塞在 IO 上并需要很长时间的线程,并且不是纯粹的 CPU 限制。
    【解决方案4】:

    Async 和 await 是标记代码位置的标记,在任务(线程)完成后控制应从何处恢复。 这是一个详细的 youtube 视频,它以示范的方式解释了这个概念http://www.youtube.com/watch?v=V2sMXJnDEjM

    如果您愿意,您还可以阅读这篇 coodeproject 文章,它以更直观的方式解释了相同的内容。 http://www.codeproject.com/Articles/599756/Five-Great-NET-Framework-4-5-Features#Feature1:-“Async”和“Await”(代码标记)

    【讨论】:

      【解决方案5】:
      static void Main(string[] args)
      {
          if (Thread.CurrentThread.Name == null)
              Thread.CurrentThread.Name = "Main";
          Console.WriteLine(Thread.CurrentThread.Name + "1");
      
          TaskTest();
      
          Console.WriteLine(Thread.CurrentThread.Name + "2");
          Console.ReadLine();
      }
      
      
      private async static void TaskTest()
      {
          Console.WriteLine(Thread.CurrentThread.Name + "3");
      
          await Task.Delay(2000);
          if (Thread.CurrentThread.Name == null)
              Thread.CurrentThread.Name = "FirstTask";
          Console.WriteLine(Thread.CurrentThread.Name + "4");
      
          await Task.Delay(2000);
          if (Thread.CurrentThread.Name == null)
              Thread.CurrentThread.Name = "SecondTask";
          Console.WriteLine(Thread.CurrentThread.Name + "5");
      }
      

      如果你运行这个程序,你会看到await 将使用不同的线程。输出:

      Main1
      Main3
      Main2
      FirstTask4 // 2 seconds delay
      SecondTask5 // 4 seconds delay
      

      但如果我们同时删除 await 关键字,您会发现单独使用 async 并没有多大作用。输出:

      Main1
      Main3
      Main4
      Main5
      Main2
      

      【讨论】:

      • "await will start new thread":你怎么知道线程是新的,而且是刚刚启动的?它可能是很久以前开始的可重用ThreadPool 线程。您可以查询Thread.IsThreadPoolThread 属性进行查找。
      • @TheodorZoulias 我的意思是“使用不同的线程”,但是是的,我没有测试它是新线程还是池中重用的线程,所以我不知道它是新的还是旧的。我编辑了我的帖子以“使用不同的线程”。
      猜你喜欢
      • 2020-03-25
      • 1970-01-01
      • 1970-01-01
      • 2020-04-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-12
      • 1970-01-01
      相关资源
      最近更新 更多