异步编程的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...
会立即打印到控制台,然后在最终找到哈希时打印结果。
实际上,在找到哈希值之前,不会向控制台打印任何内容。
问题
我的理解或代码在哪里出错?
答案 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
以观察该工作的进度。但是因为您不await
它Main
方法将继续执行并且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);
}
}