C# 使用 ValueTasks

C# 7 带有更灵活的 await 关键字;它现在可以等待任何提供 GetAwaiter 方法的对象。一种可用于等待的新类型是 ValueTask。与 Task 类相反,ValueTask 是一个结构。这具有性能优势,因为 ValueTask 在堆上没有对象。.

与异步方法调用相比,Task 对象的实际开销是多少?需要异步调用的方法通常比堆上的对象有更多的开销。大多数时候,堆上 Task 对象的开销是可以忽略的,但并不总是这样。例如,某方法可以有一个路径,其中数据是从一个具有异步 API 的服务中检索出来的。通过这种数据检索,数据就写入到本地缓存中。第二次调用该方法时,可以以快速的方式检索数据,而不需要创建 Task 对象。

示例方法 GreetingValueTaskAsync 正是这样做的。如果该名称已存在于字典中,则结果返回为 ValueTask。如果名称不在字典中,将调用 GreetingAsync 方法,该方法返回一个 Task。在此任务中等待检索结果时,将再次返回 ValueTask:

private readonly static Dictionary<string, string> names = new Dictionary<string, string>();
static async ValueTask<string> GreetingValueTaskAsync(string name)
{
  if (names.TryGetValue(name, out string result))
  {
    return result;
    }
    else
    {
      result = await GreetingAsync(name);
      names.Add(name, result); 
      return result;
    }
  }
}

UseValueTask 方法使用相同的名称调用 GreetingValueTaskAsync 方法两次。第一次使用 GreetingAsync 方法检索数据;第二次,数据在字典中找到并从那里返回:

private static async void UseValueTask()
{
  string result = await GreetingValueTaskAsync("Katharina"); 
  Console.WriteLine(result);
  string result2 = await GreetingValueTaskAsync("Katharina"); 
  Console.WriteLine(result2);
}

如果方法不使用 async 修饰符,而需要返回 ValueTask,就可以使用传递结果或者传递 Task 对象的构造函数创建 ValueTask 对象:

static ValueTask<string> GreetingValueTask2Async(string name)
{
  if(names.TryGetValue(name, out string result))
  {
    return new ValueTask<string>(result);
  }
  else
  {
    Task<string> tl = GreetingAsync(name);
    TaskAwaiter<string> awaiter = tl.GetAwaiter();
    awaiter.OnCompleted(OnCompletion); 
    return new ValueTask<string>(t1):
    void OnCompletion()
    {
      namea.Add(name, awaiter.GetResult());
    }
  }
}