异步 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);
    }
}

有没有办法让它异步?我希望使用 asyncawait 关键字,但 HashAlgorithm 类没有为此提供任何异步支持。

另一种方法是将所有逻辑封装在:

public static async string Sha256Hash(string input) {
     return await Task.Run(() => {
         //Hashing here...
     });
}

但这似乎并不干净,我不确定这是否是异步执行操作的正确(或有效)方式。

我该怎么做才能完成此操作?

异步 SHA256 哈希

正如其他回答者所说,哈希是一种 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>();

当然,您可以将该方法与其他哈希算法(如 SHA1CryptoServiceProviderSHA512CryptoServiceProvider)一起调用为泛型类型参数。

同样,通过一些修改,您也可以让它根据您的情况对字符串进行哈希处理。

您正在执行的工作本质上是同步 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,但这滥用了它的目的。 但是,例如,您可以将ComputeHashAsyncFileStream一起使用来计算文件的哈希 - 除了 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;
}