C# - 理解异步/等待

时间:2018-04-16 19:56:05

标签: c# asynchronous async-await

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

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

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

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

考虑到这一点,我测试了一些CPU绑定代码(找到一个比特币块哈希似乎是现代和困难的)试图理解当应用async / await时发生了什么:

示例

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...会立即打印到控制台,然后在最终找到哈希时打印结果。

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

问题

我的理解或代码在哪里出错?

5 个答案:

答案 0 :(得分:3)

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

我认为查看其工作原理的最简单方法是将Task从[{1}}返回的HashAsync分配给变量,而不是await,直到Console.WriteLine之后:

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以观察该工作的进度。但是因为您不awaitMain方法将继续执行并且Calculating hash...将被打印。只有当您调用await resultTask控件时,才会将其返回给调用Main的人,执行将被暂停。

答案 1 :(得分:0)

你正在等待HashAsync完全结束..

private static async Task Main(string[] args)
{
   Console.WriteLine("Calculating hash..."); // <-- this before await

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

   Console.WriteLine(result);

   Console.Read();
}

答案 2 :(得分:0)

这是因为你await

我认为理解它的最好方法就是将代码async想象成一个并发运行的代码块,但是一旦等待它,你会说:“我现在需要这个值,不会继续除非我得到它“,如果你没有等待,那么你正在处理一个可能处于不完整状态的任务。

答案 3 :(得分:0)

此处:string result = await HashAsync(Guid.NewGuid().ToByteArray(), 4);

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

对控制台的打印是在同一个调用方法中完成的,因此只要HashAsync完成,您就会看到“计算散列...”。

答案 4 :(得分:0)

当执行线程或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);
     }
  }