【问题标题】:C# - understanding async/awaitC# - 理解异步/等待
【发布时间】:2018-09-26 15:52:37
【问题描述】:

关于异步编程的 C# 文档指出:

  • 对于受 CPU 限制的代码,您等待使用 Task.Run 方法在后台线程上启动的操作。

  • await 关键字是魔法发生的地方。它将控制权交给执行 await 的方法的调用者,并最终允许 UI 具有响应性或服务具有弹性。

  • 当应用 await 关键字时,它会暂停调用方法并将控制权交还给调用者,直到等待的任务完成。

考虑到这一点,我测试了一些 CPU 绑定代码(找到一个比特币块哈希似乎非常现代且困难),以尝试准确了解应用 async/await 时发生的情况: p>

示例

namespace AsyncronousSample
{
    using System;
    using System.Linq;
    using System.Security.Cryptography;
    using System.Threading.Tasks;

    internal static class Program
    {
        private static async Task Main(string[] args)
        {
            string result = await HashAsync(Guid.NewGuid().ToByteArray(), 4);

            Console.WriteLine("Calculating hash...");
            Console.WriteLine(result);

            Console.Read();
        }

        private static async Task<string> HashAsync(byte[] data, int difficulty = 1)
        {
            int nonce = default;
            string result = default;

            byte[] GetDataBytesWithNOnce()
            {
                return data
                    .Concat(BitConverter.GetBytes(nonce++))
                    .ToArray();
            }

            byte[] ComputeHash(byte[] bytes)
            {
                using (SHA256 sha = SHA256.Create())
                {
                    return sha.ComputeHash(sha.ComputeHash(bytes));
                }
            }

            string ConvertToHash(byte[] hashBytes)
            {
                return BitConverter
                    .ToString(hashBytes)
                    .Replace("-", string.Empty)
                    .ToLower();
            }

            return await Task.Run(() =>
            {
                do
                {
                    result = ConvertToHash(ComputeHash(GetDataBytesWithNOnce()));
                } while (!result.StartsWith(new string('0', difficulty)));

                return result;
            });
        }
    }
}

好的,对于那些对比特币及其工作原理感兴趣的更精明的人来说,这不是真正的比特币哈希算法,而是SHA256(SHA256(data + nonce)),所以这已经够难了举个例子。

期望与现实

我希望Calculating hash... 会立即打印到控制台,然后在最终找到哈希时打印结果。

实际上,在找到哈希之前,控制台不会打印任何内容。

问题

我的理解或代码哪里出了问题?

【问题讨论】:

  • 那是因为你在打印之前await
  • 好吧,你等着它..
  • @SLaks “应用 await 关键字时,它会暂停调用方法并将控制权交还给调用者,直到等待的任务完成。” - 在我看来,这意味着等待的方法被暂停并且 Main 方法重新获得控制权,直到等待的方法完成,最后,Console.WriteLine(result) 解决并完成? - 我知道,我可能完全不合时宜
  • 它暂停调用方法(带有async的方法)并将控制权交还给异步方法的调用者(无论调用什么方法)
  • 想象一下 await 将方法分成两部分,之前和之后。如果您熟悉 javascript,请想象一些始终具有 'then(...)' 延续的 ajax 查询。 await 之后的部分是这样的 'then' 延续,但按顺序编写,没有“回调地狱”。

标签: c# asynchronous async-await


【解决方案1】:

因为您在HashAsync 上调用await,所以控件将交给调用者,在这种情况下是.NET Framework 本身,它调用了您的Main 方法。

我认为了解其工作原理的最简单方法是将HashAsync 返回的Task 分配给一个变量,但直到Console.WriteLine 之后才分配给await

private static async Task Main(string[] args)
{
    Task<string> resultTask = HashAsync(Guid.NewGuid().ToByteArray(), 4);

    Console.WriteLine("Calculating hash...");'

    string result = await resultTask;

    Console.WriteLine(result);

    Console.Read();
}

通过此更改,一旦您调用 HashAsync,它将使用 Task.Run 将工作推到后台,并返回一个 Task 以观察该工作的进度。但是因为你不是awaiting 它Main 方法将继续执行并且Calculating hash... 将被打印。只有当您调用await resultTask 时,控制权才会返回给调用Main 的任何人并暂停执行。

【讨论】:

    【解决方案2】:

    因为你await它。

    我认为理解它的最好方法就是将async 的代码想象成一个同时运行的代码块,但是一旦你等待它,你就会说:“我现在需要这个值,除非我明白了”,如果您不等待,那么您正在处理的任务可能处于未完成状态。

    【讨论】:

    • 如果是这样的话,这与非 async/await 代码有什么不同...我觉得我错过了一些东西。
    • @series0ne 在一个 UI 线程上尝试,一个会阻塞(挂起)UI,另一个会返回给 UI。
    【解决方案3】:

    这里:string result = await HashAsync(Guid.NewGuid().ToByteArray(), 4);

    您正在暂停调用者方法(在您的情况下是您的 Main),一旦“等待”调用完成,它将继续。

    打印到控制台,是在同一个调用者方法中完成的,所以一旦HashAsync 完成,您将看到“正在计算哈希...”。

    【讨论】:

      【解决方案4】:

      当执行线程或UI线程执行一行以await开头的代码时,UI线程会自动解除阻塞,等待操作完成,而用户可以与UI交互,一段时间后异步操作完成然后代码将开始执行,如果它首先离开它。

      更新

      c#7.1中main方法也可以是async,async await可以这样使用

        class Program
        {
            public static async Task Main(string[] args)
            {
               await Task.Run(async () =>
               {
                  MyAsyncFunc();
               });
               Console.WriteLine("done");
               Console.ReadLine();
            }
      
          static async Task MyAsyncFunc()
           {
              await Task.Delay(3000);
           }
        }
      

      【讨论】:

      • 在控制台应用程序中怎么样?
      • 在控制台应用程序中,Main 方法不能是异步的,因此您必须运行任务并手动使其等待。
      • 但是,我的 Main 方法是异步的(根据它在 C# 7.1 中的介绍)
      • C# 7.1 确实支持 Main 方法中的异步,要实现您的目标,您可以像 await Task.Run(async () => { HashAsync(Guid.NewGuid().ToByteArray() , 4); });它会产生预期的结果。
      • 我自己测试过,控制台会先打印计算哈希
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-16
      • 2017-11-04
      • 1970-01-01
      • 1970-01-01
      • 2021-02-28
      • 1970-01-01
      相关资源
      最近更新 更多