#showdev如何使用.NET Core,C#和VS Code中的异步代码使您的.NET程序具有更高的响应速度

在Twitter上关注我,乐意接受您对主题或改进的建议/ Chris

当我们运行同步代码时,我们将阻止主线程执行除当前正在执行的操作以外的任何其他操作。这会使您的软件和用户体验变慢。

TLDR;我们在.NET / .NET Core中拥有线程的概念,它们是安排并行执行工作的绝佳方法。但是,使用它们可能很麻烦。但是,有一个称为TPL的库,即任务并行库,它位于Thread模型的顶部,使计划和管理工作变得非常容易。

参考文献

  • 异步返回类型
    它为您很好地介绍了Tasks。

  • 任务控制流程
    这里讨论控制流,如何确保代码以正确的顺序发生

  • 基于任务的异步编程
    这更多是基于任务的编程的概述

  • 如何顺序运行任务
    这里讨论如何依次运行Tasks。

  • 任务取消
    这将教您如何取消和收听任务的取消消息

  • Azure中的持久功能
    这显示了如何在无服务器编程中执行任务,特别是在持久函数中

  • 将同步代码转换为异步的食谱
    这使您从同步代码一直逐步过渡到异步代码。

什么

因此,我们提到了TPL作为一个库。我们需要知道些什么? TPL是一个非常重要的核心概念,它存在于核心API中。这是 System.ThreadingSystem.Threading.Tasks 命名空间。它对我们有很多帮助,例如:

  • 工作分区

  • 线程调度 ThreadPool

  • 取消支援

  • 国家管理

和其他低级详细信息。

我们需要了解一些基本概念。

  • 任务,任务代表异步操作,例如从文件中获取内容或进行计算需要时间。 Task上有一些有趣的属性,可让我们与UI进行通信,例如,异步工作的方式,例如:

    • 状态,可以告诉我们它是否正在处理某件事,已完成,出错或已被取消

    • IsCanceled,如果取消,则将其设置为 true

    • IsFaulted,如果出现问题(例如异常),则将其设置为 true

    • IsCompleted,一旦完成操作,它将设置为 true

  • 异步/等待。的 await 关键字表示完成异步操作,并在操作结束时得到结果,例如 var fileContent = await GetFileAsync()。任何使用 await 概念将需要 async 关键字作为方法标题的一部分。

  • 阻止/非阻止。使用Task时,我们不会阻塞,其他线程可以执行工作。尽管当我们使用该方法时也有例外 Wait() 在Task上,我们强制代码同步运行。我们将在下一部分的演示中展示这一点。

为什么

可以并行完成许多事情,例如打开大文件或执行Web请求或通过计算机搜索。这意味着您可以更快地将结果返回给用户,并且您的应用将被视为更快,响应速度更快。 Web开发已经大量使用了任务的概念,这是TPL中的核心概念。学习如何使用TPL确实可以使您的应用程序具有更高的响应速度。我希望您对本文有所了解,可以使用TPL和Tasks。

演示

在我们的演示中,我们将演示以下内容:

  • 创作方法,如何使用创作方法 async/await 以及如何返回不同的类型

  • 控制流,我们将展示如何等待所有以及特定的任务

  • 阻塞代码,我们将展示如何使用 Result 以及 Wait() 影响您的代码

脚手架项目

让我们从创建这样的解决方案开始:

mkdir tasks
cd tasks

dotnet new sln

这应该创建一个解决方案文件。

接下来,我们将创建一个控制台项目,如下所示:

dotnet new solution -o task-demo

然后将其添加到解决方案中,如下所示:

dotnet sln add task-demo/task-demo.csproj

好的,我们准备开始编码了。打开IDE,我要使用VS Code。

创作方法

让我们打开文件 Program.cs 并在类中添加以下方法 Program

static async Task<int> Sum(int a, int b) 
{
    var result = await Task.FromResult(a + b);
    return result;
}

上面有一些有趣的事情:

  • 返回类型 Task。这告诉我们这将是一个Task,一旦解决,它将返回类型 int

  • Task.FromResult(),这将创建一个赋予值的Task。我们给它执行计算,例如 a+b
  • 异步/等待,我们可以看到我们如何使用 async 方法内部的关键字,以等待结果返回给我们。这之后需要 async 关键字,以确保编译器满意。

很容易想到上面的方法不需要异步,而是想像一下这是一个需要时间的计算,那么它会更有意义。

控制流

任务不仅仅是标记它们 async。我们可以确保在继续执行代码之前等待所有或某些任务完成。我们有一些构造可以帮助我们控制这种流动:

  • Task.WaitAll(),这是一份“任务”清单。您实际上要说的是,所有任务都必须先完成才能继续进行,这是阻塞的。您可以看到它返回 void
    一个典型的用例是等待所有Web请求完成,因为我们要返回一个结果,该结果包括将所有数据缝合在一起
  • Task.WaitAny(),我们也会在此处为其提供任务列表,但含义有所不同。我们说只要任何一项任务完成就可以了。通常,这是争夺端点数据或在磁盘上搜索文件的内容。只要得到回应,我们就不在乎先完成什么。这也阻止并等待任务之一完成
  • Task.WhenAll(),这给你一个 Task 您可以与之互动的背面。所有任务完成后,它将解决。
  • Task.WhenAny(),这给你一个 Task 您可以与之互动的背面。任务之一完成后,它将解决。

让我们创建一个控制流的演示。我们将在类中添加其他方法来伪装执行耗时的工作,如下所示:

static async Task DoSomething()
{
  await Task.Delay(2000);
}

演示-控制流程

现在我们可以在我们的控件中添加一些控制流代码 Main() 像这样的方法:

var start  = DateTime.Now;

var taskSum = Sum(2,2);
var taskDelay = DoSomething();
Task.WaitAll(taskSum, taskDelay);

end = DateTime.Now;

Console.WriteLine("Time taken {0}",end - start);

我们的完整代码在 Program.cs 现在应该看起来像这样:

using System;
using System.Threading.Tasks;
using System.IO;

namespace task_demo
{
    class Program
    {
        static async Task DoSomething()
        {
            await Task.Delay(2000);
        }
        static async Task<int> Sum(int a, int b) 
        {
            var result = await Task.FromResult(a + b);
            return result;
        }

        static void Main(string() args)
        {
            var start  = DateTime.Now;

            var taskSum = Sum(2,2);
            var taskDelay = DoSomething();

            Task.WaitAll(taskSum, taskDelay);

            end = DateTime.Now;

            Console.WriteLine("Time taken! {0}", end-start);
        }
    }
}

让我们编译:

dotnet build

并运行它:

dotnet run

我们应该得到以下回应:

4
Time taken! 00:00:02.0026920

即使通过调用计算 Sum() 花费了几毫秒的时间,直到2秒后,我们才收到任何回复 DoSomething() 已完成。

如果我们现在将代码从 WaitAllWhenAll 我们将得到截然不同的行为。该代码将继续前进,并报告此:

4
Time taken! 00:00:00.0235860

因此,这里的教训是,如果我们希望代码在特定点等待,请使用 WaitAny 是个好主意,但是如果您要启动大量异步工作,请使用 When...

我们仍然可以使代码正常运行 WhenAll 但是我们需要像这样调查状态:

var twoTasks = Task.WhenAll(taskSum, taskDelay);
if(twoTasks.IsCompleted) 
{
    var end = DateTime.Now;
    Console.WriteLine("{0}", taskSum.Result);
}

演示-等待任何

为了测试这一点,我们创建了三种模拟打开文件的新方法。三种方法中的每一种都有不同的内置延迟:

static async Task<string> ReadFile1() 
{
    await Task.Delay(3000);
    return "file1";
}

static async Task<string> ReadFile2()
{
    await Task.Delay(4000);
    return "file2";
}

static async Task<string> ReadFile3()
{
    await Task.Delay(2000);
    return "file3";
}

让我们更新我们的 Program() 方法以及一些代码:

var task1 = ReadFile1();
var task2 = ReadFile2();
var task3 = ReadFile3();

start = DateTime.Now;
Task.WaitAny(task1, task2, task3);


Console.WriteLine("Task1, completed: {0}", task1.IsCompleted);

Console.WriteLine("Task2, completed: {0}", task2.IsCompleted);

Console.WriteLine("Task3, completed: {0}", task3.IsCompleted);
Console.WriteLine("Task3, completed: {0}", task3.Result);

end = DateTime.Now;
Console.WriteLine("Time taken! {0}", end - start);

正如您在上面看到的,使用此结构,我们正在等待三个任务之一完成:

Task.WaitAny(task1, task2, task3);

鉴于我们对所调用方法的了解, ReadFile3() 应该先完成 2 秒,但让我们通过运行程序来测试一下:

Task1, completed: False
Task2, completed: False
Task3, completed: True
Task3, completed: file3
Time taken! 00:00:02.0031370

我们可以在上面看到 Task3 已完成,其他任务尚未完成。

使用异步API

好的,我们现在对异步有了更多的了解,并且能够在现有的API上利用它。让我们看一下读取文件的内容。通常,您将创建如下方法:

 static async string ReadTxtFile() 
{
    using(var sr = new StreamReader(File.Open("test.txt", FileMode.Open)))
    {
        return sr.ReadToEnd();
    }
}

上面的内容虽然会阻止,但在完成时您将无法做其他事情。想象这是一个非常大的文件,那么它将非常引人注目。如果我们重写该方法以使用异步版本,我们将获得如下代码:

 static async Task<string> ReadTxtFile() 
{
    using(var sr = new StreamReader(File.Open("test.txt", FileMode.Open)))
    {
        return await sr.ReadToEndAsync();
    }
}

这不会阻止,每个人都很高兴。

阻止代码

使用TPL的棘手部分之一就是知道什么叫阻塞。大家都很高兴您的代码现在是异步的,但是突然之间您最终还是阻塞了。那么我们要注意什么呢?好吧,我们已经谈到了这个主题:

  • WaitAllWaitAny 障碍,这里的经验法则似乎是它们返回void并使用单词Wait ….有时,尽管您希望它等待,所以要学会故意使用阻塞/非阻塞
  • task.Result,这也会阻止并等待结果可用
  • Wait(),则Task上的此方法将被阻止,并导致您在此处等待,直到代码完成为止,例如 Task.Delay(2000).Wait()

完整代码

如果您想自己探索,这是我正在使用的完整代码:

using System;
using System.Threading.Tasks;
using System.IO;

namespace task_demo
{
  class Program
  {
      static async Task<string> ReadTxtFile() 
      {
          using(var sr = new StreamReader(File.Open("test.txt", FileMode.Open)))
          {
              return await sr.ReadToEndAsync();
          }
      }

      static string ReadFileSync1() 
      {
          Task.Delay(2000).Wait();
          return "content1";
      }

      static string ReadFileSync2()
      {
          Task.Delay(2000).Wait();
          return "content2";
      }

      static string ReadFileSync3()
          {
          Task.Delay(2000).Wait();
          return "content3";
      }

      static async Task DoSomething()
      {
          await Task.Delay(2000);
      }
      static async Task<int> Sum(int a, int b) 
      {
          var result = await Task.FromResult(a + b);
          return result;
      }

      static async Task<string> ReadFile1() 
      {
          await Task.Delay(3000);
          return "file1";
      }

      static async Task<string> ReadFile2()
      {
          await Task.Delay(4000);
          return "file2";
      }

      static async Task<string> ReadFile3()
      {
          await Task.Delay(2000);
          return "file3";
      }

      static void Main(string() args)
      {
          var start = DateTime.Now;
          var c1 = ReadFileSync1();
          var c2 = ReadFileSync2();
          var c3 = ReadFileSync3();
          var end = DateTime.Now;
          Console.WriteLine("Time taken {0}", end-start);

          start  = DateTime.Now;

          var taskSum = Sum(2,2);
          var taskDelay = DoSomething();

          Task.WaitAll(taskSum, taskDelay);

          end = DateTime.Now;

          Console.WriteLine("{0}",taskSum.Result);

          Console.WriteLine("Time taken! {0}", end-start);

          var task1 = ReadFile1();
          var task2 = ReadFile2();
          var task3 = ReadFile3();

          start = DateTime.Now;
          Task.WaitAny(task1, task2, task3);


          Console.WriteLine("Task1, completed: {0}", task1.IsCompleted);

          // this forces everyone to wait for this Task1
          // Console.WriteLine("Task1, completed: {0}", task1.Result);

          Console.WriteLine("Task2, completed: {0}", task2.IsCompleted);

          Console.WriteLine("Task3, completed: {0}", task3.IsCompleted);
          Console.WriteLine("Task3, completed: {0}", task3.Result);

          end = DateTime.Now;
          Console.WriteLine("Time taken! {0}", end - start);

      }
  }
}

摘要

总而言之,我们了解了任务的概念及其解剖结构。此外,我们了解了控制流,还讨论了阻塞/非阻塞代码。还有更多要学习的内容,例如如何取消任务。我要把那个保存下来另写一篇文章。我将在本文的“参考”部分中添加一个指向“取消”的链接。

资讯来源:由0x资讯编译自DEV,原文:https://dev.to/dotnet/how-you-can-make-your-net-programs-faster-with-asynchronous-code-in-net-core-c-and-vs-code-471c ,版权归作者所有,未经许可,不得转载
你可能还喜欢