异步 SHA256 哈希
本文关键字:哈希 SHA256 异步 | 更新日期: 2023-09-27 18:31:51
>我有以下方法:
public static string Sha256Hash(string input) {
if(String.IsNullOrEmpty(input)) return String.Empty;
using(HashAlgorithm algorithm = new SHA256CryptoServiceProvider()) {
byte[] inputBytes = Encoding.UTF8.GetBytes(input);
byte[] hashBytes = algorithm.ComputeHash(inputBytes);
return BitConverter.ToString(hashBytes).Replace("-", String.Empty);
}
}
有没有办法让它异步?我希望使用 async 和 await 关键字,但 HashAlgorithm
类没有为此提供任何异步支持。
另一种方法是将所有逻辑封装在:
public static async string Sha256Hash(string input) {
return await Task.Run(() => {
//Hashing here...
});
}
但这似乎并不干净,我不确定这是否是异步执行操作的正确(或有效)方式。
我该怎么做才能完成此操作?
正如其他回答者所说,哈希是一种 CPU 密集型活动,因此它没有可以调用的异步方法。但是,您可以通过逐块异步读取文件,然后对从文件中读取的字节进行哈希处理,从而使哈希方法异步。哈希将同步完成,但读取将是异步的,因此您的整个方法将是异步的。
这是实现我刚才描述的目的的示例代码。
public static async Threading.Tasks.Task<string> GetHashAsync<T>(this Stream stream)
where T : HashAlgorithm, new()
{
StringBuilder sb;
using (var algo = new T())
{
var buffer = new byte[8192];
int bytesRead;
// compute the hash on 8KiB blocks
while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) != 0)
algo.TransformBlock(buffer, 0, bytesRead, buffer, 0);
algo.TransformFinalBlock(buffer, 0, bytesRead);
// build the hash string
sb = new StringBuilder(algo.HashSize / 4);
foreach (var b in algo.Hash)
sb.AppendFormat("{0:x2}", b);
}
return sb?.ToString();
}
可以这样调用该函数
using (var stream = System.IO.File.OpenRead(@"C:'path'to'file.txt"))
string sha256 = await stream.GetHashAsync<SHA256CryptoServiceProvider>();
当然,您可以将该方法与其他哈希算法(如 SHA1CryptoServiceProvider
或 SHA512CryptoServiceProvider
)一起调用为泛型类型参数。
同样,通过一些修改,您也可以让它根据您的情况对字符串进行哈希处理。
您正在执行的工作本质上是同步 CPU 密集型工作。 它本质上不是异步的,就像网络 IO 一样。 如果您想在另一个线程中运行一些同步 CPU 密集型工作并异步等待它完成,那么Task.Run
确实是完成此操作的合适工具,假设该操作运行时间足够长,需要异步执行它。
也就是说,实际上没有任何理由在同步方法上公开异步包装器。 通常,仅同步公开方法更有意义,如果特定调用方需要它在另一个线程中异步运行,则可以使用 Task.Run
显式指示该特定调用的需要。
异步运行(使用 Task.Run)的开销可能高于同步运行它。
异步接口不可用,因为它是 CPU 密集型操作。正如您所指出的,您可以使其异步(使用 Task.Run),但我建议不要这样做。
从 .NET 5(自 2020 年 11 月起可用)开始,您可以使用一个哈希算法.计算哈希异步... 但是,正如上面的其他答案所指出的那样,计算哈希是 CPU 密集型操作,但async
任务通常旨在解决 I/O 密集型操作。 对于任何开始异步编程之旅的人来说,这是一个很好的差异例子。
需要注意的重要一点是,ComputeHashAsync 不提供适用于 byte[]
的签名。 它只提供一个在Stream
上运行的版本:
byte[] ComputeHash(byte[]);
byte[] ComputeHash(Stream);
byte[] ComputeHash(byte[], int, int);
Task<byte[]> ComputeHashAsync(Stream, CancellationToken);
为什么?? 正是因为那些接受byte[]
的方法受 CPU 限制,而不是 I/O 限制,因此几乎没有理由提供这些签名的async
版本。 因此,即使它可以编译并且工作正常,您也不想做这样的事情:
async Task<byte[]> BadComputeHashAsync(string input = "Don't do this")
{
byte[] hash, inputBytes = Encoding.UTF8.GetBytes(input);
// Get a MemoryStream so ComputeHashAsync can be used (bad idea!)
using (MemoryStream ms = new MemoryStream(inputBytes))
hash = await MD5.Create().ComputeHashAsync(ms);
return hash;
}
使用MemoryStream
将字节转换为流确实允许您使用ComputeHashAsync
,但这滥用了它的目的。 但是,例如,您可以将ComputeHashAsync
与FileStream
一起使用来计算文件的哈希 - 除了 CPU 绑定(计算哈希)之外,还将是 I/O 绑定(读取文件)。 这是一个很好的用法:
async Task<byte[]> ComputeFileHash(string filename)
{
byte[] hash;
using (FileStream fs = File.OpenRead(filename))
hash = await MD5.Create().ComputeHashAsync(fs);
return hash;
}