【问题标题】:How to write an "awaitable" method?如何编写“等待”方法?
【发布时间】:2014-02-11 11:41:47
【问题描述】:

我终于研究了 async 和 await 关键字,我有点“get”,但我见过的所有示例都在 .Net 框架中调用 async 方法,例如this one,它调用HttpClient.GetStringAsync()

我不太清楚的是这种方法会发生什么,以及我将如何编写自己的“等待”方法。是否像将要异步运行的代码包装在 Task 中并返回一样简单?

【问题讨论】:

  • 当然也可以编写一个返回TaskTask<T> 的异步方法。异步以这种方式可以很好地组合。
  • 我只是在学习 C#。我正在查看与 GetStringAsync() 相同的示例,尽管我是资深的 Java 多线程程序,但我遇到了与 OP 完全相同的问题。这是一个很好的问题,可能应该在 MSDN 文章中解决,否则它非常完整。

标签: c# async-await


【解决方案1】:

就这么简单

Task.Run(() => ExpensiveTask());

使其成为可等待的方法:

public Task ExpensiveTaskAsync()
{
    return Task.Run(() => ExpensiveTask());
}

这里重要的是返回一个任务。该方法甚至不必标记为异步。 (只需进一步阅读,它就会出现在图片中)

现在这可以称为

async public void DoStuff()
{
    PrepareExpensiveTask();
    await ExpensiveTaskAsync();
    UseResultsOfExpensiveTask();
}

注意这里的方法签名是async,因为在ExpensiveTaskAsync()返回之前,该方法可能会将控制权返回给调用者。此外,在这种情况下,昂贵意味着耗时,例如 Web 请求或类似请求。要将繁重的计算发送到另一个线程,通常最好使用“旧”方法,即System.ComponentModel.BackgroundWorker 用于 GUI 应用程序或System.Threading.Thread

【讨论】:

  • 创建可等待方法的最自然和最常用的方法是编写一个返回TaskTask<> 的方法,这是正确的。但从技术上讲,您也可以编写一个返回 YourOwnType 的方法,前提是 YourOwnType 有一个名为 GetAwaiter() 的公共无参数非静态实例方法,其返回类型是适当的(在其他地方查找详细信息)。所以await 有点像foreach,它适用于任何具有合适公共方法的类型。
  • 很高兴知道!虽然如果您决定使用这种方法,它可能不会帮助您的代码的可读性。
  • @JeppeStigNielsen foreach 需要 IEnumerable。当界面更适合该语言时,await 使用鸭式打字让我有点失望。它是“供编译器使用”的事实是一个糟糕的借口。
  • @Gusdor 为什么,foreach 不需要任何接口!试试这些类:class ForeachMe { public StrangeType GetEnumerator() { return new StrangeType(); } } class StrangeType { public bool MoveNext() { return true; } public DateTime Current { get { return DateTime.Now; } } } 有了它们,代码foreach (var x in new ForeachMe()) { Console.WriteLine(x); } 就可以正常工作了。
  • @Gusdor 权威来源是官方 C# 语言规范中的 The foreach statement (old version) 部分。在newest version 中找到相同的部分。这是明确规定的;该类型不需要实现IEnumerableIEnumerable<>
【解决方案2】:

我将如何编写自己的“等待”方法?是否就像将我想要异步运行的代码包装在 Task 中并返回一样简单?

这是一种选择,但它很可能不是您想要做的,因为它实际上并没有为您提供异步代码的许多优点。更多详情请见Stephen Toub's Should I expose asynchronous wrappers for synchronous methods?

一般来说,方法是不可等待的,类型是。如果您希望能够编写类似await MyMethod() 的东西,那么MyMethod() 必须返回TaskTask<T> 或自定义awaitable 类型。使用自定义类型是一种罕见且高级的场景;使用Task,您有多种选择:

  • 使用asyncawait 编写您的方法。这对于异步组合操作很有用,但它不能用于最内层的awaitable 调用。
  • 使用Task 上的一种方法创建Task,例如Task.Run()Task.FromAsync()
  • 使用TaskCompletionSource。这是最通用的方法,它可用于根据将来发生的任何事情创建 awaitable 方法。

【讨论】:

    【解决方案3】:

    ...我将如何编写自己的“等待”方法。

    返回Task 不是唯一的方法。您可以选择创建一个自定义等待者(通过实现GetAwaiterINotifyCompletion),这是一个很好的阅读:"Await anything"。返回自定义等待者的 .NET API 示例:Task.Yield()Dispatcher.InvokeAsync

    我有一些带有自定义等待者 herehere 的帖子,例如:

    // don't use this in production
    public static class SwitchContext
    {
        public static Awaiter Yield() { return new Awaiter(); }
    
        public struct Awaiter : System.Runtime.CompilerServices.INotifyCompletion
        {
            public Awaiter GetAwaiter() { return this; }
    
            public bool IsCompleted { get { return false; } }
    
            public void OnCompleted(Action continuation)
            {
                ThreadPool.QueueUserWorkItem((state) => ((Action)state)(), continuation);
            }
    
            public void GetResult() { }
        }
    }
    
    // ...
    
    await SwitchContext.Yield();
    

    【讨论】:

    • // don't use this in production — 为什么?
    • @hypersw, here is why.
    • 不能把它当作一个很好的“为什么”,但对于我已经追踪到链接的一个具体细节,关于awaits 在finallys 中不可用及其后果,反正不再持有了。其余的都是推测性的,比如看起来很糟糕的代码(Task::StartNew 每个部分而不是await YieldTo?只有你自己没有尝试过),或者语义不清楚(与ConfigureAwait(false)相反,我猜? )。
    • 我想这一切都归结为情景。如果你在一个函数中有十几个上下文切换,它会有所帮助。如果您只启动大型后台任务,那么您会更安全。
    • “等待任何事情”链接已失效。我认为这是新链接:devblogs.microsoft.com/pfxteam/await-anything
    【解决方案4】:

    是的,从技术上讲,您只需要从 async 方法返回 TaskTask<Result> 即可实现可等待的方法。

    这支持Task-Based Asynchronous Pattern

    但是,有几种方法可以实现 TAP。详情请见Implementing the Task-based Asynchronous Pattern

    (当然,所有这些实现仍然返回TaskTask<Result>。)

    【讨论】:

      【解决方案5】:

      只需将您的方法转换为任务。像@Romiox 我通常使用这个扩展名:

      public static partial class Ext
      {
          #region Public Methods
          public static Task ToTask(Action action)
          {
              return Task.Run(action);
          }
          public static Task<T> ToTask<T>(Func<T> function)
          {
              return Task.Run(function);
          }
          public static async Task ToTaskAsync(Action action)
          {
              return await Task.Run(action);
          }
          public static async Task<T> ToTaskAsync<T>(Func<T> function)
          {
              return await Task.Run(function);
          }
          #endregion Public Methods
      }
      

      现在让我们说你有

      void foo1()...
      
      void foo2(int i1)...
      
      int foo3()...
      
      int foo4(int i1)...
      

      ...

      然后你可以像@Romiox 一样声明你的[async Method]

      async Task foo1Async()
      {
          return await Ext.ToTask(() => foo1());
      }
      async Task foo2Async(int i1)
      {
          return await Ext.ToTask(() => foo2(i1));
      }
      async Task<int> foo3Async()
      {
          return await Ext.ToTask(() => foo3());
      }
      async Task<int> foo4Async(int i1)
      {
          return await Ext.ToTask(() => foo4(i1));
      }
      

      async Task foo1Async()
      {
          return await Ext.ToTaskAsync(() => foo1());
      }
      async Task foo2Async(int i1)
      {
          return await Ext.ToTaskAsync(() => foo2(i1));
      }
      async Task<int> foo3Async()
      {
          return await Ext.ToTaskAsync(() => foo3());
      }
      async Task<int> foo4Async(int i1)
      {
          return await Ext.ToTaskAsync(() => foo4(i1));
      }
      

      ...

      现在您可以对任何 fooAsync 方法使用 async 和 await,例如foo4Async

      async Task<int> TestAsync () {
          ///Initial Code
          int m = 3;
          ///Call the task
          var X = foo4Async(m);
          ///Between
          ///Do something while waiting comes here
          ///..
          var Result = await X;
          ///Final
          ///Some Code here
          return Result;
      }
      

      【讨论】:

      • 您的示例中的 await 关键字在哪里?
      【解决方案6】:

      如果您不想使用Task,您可以编写一个完全自定义的等待对象。这样的对象是一个实现方法GetAwaiter () 的对象,它返回一个实现INotifyCompletion 的对象,它可以是对象本身。

      更多:INotifyCompletion

      等待者实现:

      • IsCompleted 是获取状态
      • GetResult ()得到结果
      • OnCompleted (Action continuation) 设置延续委托。

      awaitable 对象包含一些实际有效载荷的方法(例如,下面的方法是Run)。

      class Program {
          // Need to change the declaration of Main() in order to use 'await'
          static async Task Main () {
              // Create a custom awaitable object
              MyAwaitable awaitable = new MyAwaitable ();
      
              // Run awaitable payload, ignore returned Task
              _ = awaitable.Run ();
      
              // Do some other tasks while awaitable is running
              Console.WriteLine ("Waiting for completion...");
      
              // Wait for completion
              await awaitable;
      
              Console.WriteLine ("The long operation is now complete. " + awaitable.GetResult());
          }
      }
      
      public class MyAwaitable : INotifyCompletion {
          // Fields
          private Action continuation = null;
          private string result = string.Empty;
      
          // Make this class awaitable
          public MyAwaitable GetAwaiter () { return this; }
      
          // Implementation of INotifyCompletion for the self-awaiter
          public bool IsCompleted { get; set; }
          public string GetResult () { return result; }
          public void OnCompleted (Action continuation) {
              // Store continuation delegate
              this.continuation = continuation;
              Console.WriteLine ("Continuation set");
          }
      
          // Payload to run
          public async Task Run () {
              Console.WriteLine ("Computing result...");
      
              // Wait 2 seconds
              await Task.Delay (2000);
              result = "The result is 10";
      
              // Set completed
              IsCompleted = true;
      
              Console.WriteLine ("Result available");
      
              // Continue with the continuation provided
              continuation?.Invoke ();
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2013-02-13
        • 1970-01-01
        • 2019-07-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-05-14
        相关资源
        最近更新 更多