异步返回多个值

问题描述 投票:0回答:3

我需要计算这两个独立的任务。以前我是按顺序进行的:

string firstHash = CalculateMD5Hash("MyName");
string secondHash = CalculateMD5Hash("NoName");

[方法calculateMD5Hash看起来像。它用于为最大16GB的文件计算MD5哈希值:

private string CalculateMD5(string filename)
{
    using (var md5 = MD5.Create())
    {
        using (var stream = File.OpenRead(filename))
        {
            var hash = md5.ComputeHash(stream);
            return BitConverter.ToString(hash).Replace("-", string.Empty).ToLowerInvariant();
        }
    }
}

但是由于这2个CalculateMD5Hash方法可以并行运行,因此我正在尝试这样做:

Task<string> sequenceFileMd5Task = CalculateMD5("MyName");
Task<string> targetFileMD5task = CalculateMD5("NoName");
string firstHash = await sequenceFileMd5Task;
string secondHash = await targetFileMD5task;

而且我的CalculateMD5方法看起来像:

private async Task<string> CalculateMD5(string filename)
{
    using (var md5 = MD5.Create())
    {
        using (var stream = File.OpenRead(filename))
        {
            var hash = md5.ComputeHash(stream);
            return BitConverter.ToString(hash).Replace("-", string.Empty).ToLowerInvariant();
        }
    }
}

我希望代码异步工作,但它可以同步工作。

c# async-await task-parallel-library
3个回答
3
投票

这可能是受I / O限制的,所以并行化它可能不会使事情加速很多(甚至可能使事情减速)。

已经说过,您的代码存在的问题是您没有创建任何新任务来在后台运行代码(仅指定async不会创建任何线程)。

而不是试图“强制”使用异步,最简单的解决方案可能是通过PLinq利用AsParallel

AsParallel

如果要限制用于此目的的线程数,可以按照以下示例使用List<string> files = new List<string>() { "MyName", "NoName" }; var results = files.AsParallel().Select(CalculateMD5).ToList(); ,它将并行线程数限制为2:

WithDegreeOfParallelism()

但是请注意,如果存在诸如var results = files.AsParallel().WithDegreeOfParallelism(2).Select(CalculateMD5).ToList(); 之类的东西,您当然希望将其与MD5.COmputeHashAsync()async/await一起使用-但这种事物不存在。


1
投票

您可以将功能主体更改为任务,然后等待结果。

Task.WhenAll()

0
投票

加快速度的一种方法是使用双缓冲,这样一个线程可以从文件读入一个缓冲区,而正在为另一个缓冲区计算MD5。

这使您可以将I / O与计算重叠。

最好的方法是让一个任务负责计算所有数据块的Md5,但是由于这会使代码复杂化很多(并且不太可能产生更好的结果)应该为每个块创建一个新任务。

代码看起来像这样:

private async Task<string> CalculateMD5(string filename)
{
    return await Task.Run(() =>
    {
        using (var md5 = MD5.Create())
        {
            using (var stream = File.OpenRead(filename))
            {
                var hash = md5.ComputeHash(stream);
                return BitConverter.ToString(hash).Replace("-", string.Empty).ToLowerInvariant();
            }
        }
    });
}

这里是一个可编译的测试应用程序:

使用系统;使用System.Diagnostics;使用System.IO;使用System.Security.Cryptography;使用System.Threading.Tasks;

命名空间演示{班级计划{静态异步任务Main(){字符串文件=“您要测试的大文件”;

public static async Task<byte[]> ComputeMd5Async(string filename)
{
    using (var md5  = MD5.Create())
    using (var file = File.OpenRead(filename))
    {
        const int BUFFER_SIZE = 4 * 1024 * 1024; // Adjust buffer size to taste.

        byte[] buffer1 = new byte[BUFFER_SIZE];
        byte[] buffer2 = new byte[BUFFER_SIZE];
        byte[] buffer  = buffer1; // Double-buffered, so use 'buffer' to switch between buffers.

        var task = Task.CompletedTask;

        while (true)
        {
            buffer = (buffer == buffer1) ? buffer2 : buffer1; // Swap buffers for double-buffering.
            int n = await file.ReadAsync(buffer, 0, buffer.Length);

            await task;
            task.Dispose();

            if (n == 0)
                break;

            var block = buffer;
            task = Task.Run(() => md5.TransformBlock(block, 0, n, null, 0));
        }

        md5.TransformFinalBlock(buffer, 0, 0);

        return md5.Hash;
    }
}

}

而且我得到的文件大小约为2.5GB的结果:

        Stopwatch sw = new Stopwatch();

        for (int i = 0; i < 4; ++i) // Try several times.
        {
            sw.Restart();

            var hash = await ComputeMd5Async(file);

            Console.WriteLine("ComputeMd5Async() Took " + sw.Elapsed);
            Console.WriteLine(string.Join(", ", hash));
            Console.WriteLine();

            sw.Restart();

            hash = ComputeMd5(file);

            Console.WriteLine("ComputeMd5() Took " + sw.Elapsed);
            Console.WriteLine(string.Join(", ", hash));
            Console.WriteLine();
        }
    }

    public static byte[] ComputeMd5(string filename)
    {
        using var md5    = MD5.Create();
        using var stream = File.OpenRead(filename);

        md5.ComputeHash(stream);

        return md5.Hash;
    }

    public static async Task<byte[]> ComputeMd5Async(string filename)
    {
        using (var md5  = MD5.Create())
        using (var file = File.OpenRead(filename))
        {
            const int BUFFER_SIZE = 4 * 1024 * 1024; // Adjust buffer size to taste.

            byte[] buffer1 = new byte[BUFFER_SIZE];
            byte[] buffer2 = new byte[BUFFER_SIZE];
            byte[] buffer  = buffer1; // Double-buffered, so use 'buffer' to switch between buffers.

            var task = Task.CompletedTask;

            while (true)
            {
                buffer = (buffer == buffer1) ? buffer2 : buffer1; // Swap buffers for double-buffering.
                int n = await file.ReadAsync(buffer, 0, buffer.Length);

                await task;
                task.Dispose();

                if (n == 0)
                    break;

                var block = buffer;
                task = Task.Run(() => md5.TransformBlock(block, 0, n, null, 0));
            }

            md5.TransformFinalBlock(buffer, 0, 0);

            return md5.Hash;
        }
    }
}

因此异步双缓冲版本的运行速度提高了约50%。

可能有更快的方法,但这是一个相当简单的方法。

© www.soinside.com 2019 - 2024. All rights reserved.