异步任务减慢代码(哈希)

时间:2018-05-28 09:54:36

标签: c# asynchronous hash

我正在编写一个Windows表单应用程序,它在单词列表上执行哈希。为了确保在散列处理过程中应用程序不会冻结,我使用异步任务来执行散列。但是,这样做会导致处理哈希值的速度从每秒数千个下降到每秒60个左右。

我的散列函数就像这样

private static string MD5Hash(string word)
{
    var stringBuilder = new StringBuilder();
    var MD5 = new MD5CryptoServiceProvider();
    var bytes = MD5.ComputeHash(new UTF8Encoding().GetBytes(word));

    foreach (var value in bytes)
    {
        stringBuilder.Append(value.ToString("X2"));
    }
    return stringBuilder.ToString();
}

我实现散列函数来散列像这样的文件中的单词(这不使用async,每秒可以实现几千个哈希值)

private static void DoHashes()
{
    foreach (var word in File.ReadLines("the file path"))
    { 
        File.AppendAllText("the output path",  MD5Hash(word) + Environment.NewLine);         
    }   
}

然后我使用异步任务来避免像我这样冻结我的Windows窗体应用程序(这会导致哈希的速度降至每秒60左右)

private static async void DoHashes()
{
    await Task.Run(() =>
    {       
        foreach (var word in File.ReadLines("the file path"))
        { 
            File.AppendAllText("the output path",  MD5Hash(word) + Environment.NewLine);      
        }  
    });
}

如何在不执行哈希值的情况下使窗体形状冻结,以避免速度降低?

3 个答案:

答案 0 :(得分:4)

我使用WPF应用程序完成了此测试。在我的测试环境中,我使用了5000行的文件。以下是回复

|-------------------------------------------------------------------------------------
|#        Description                                            Time Taken (in secs)
|-------------------------------------------------------------------------------------
|1    Without Async/Await (As mentioned in the question)            144.933
|2    With Async/Await (As mentioned in the question)               145.563
|3    Using StringBuilder and writing to file only once             0.143
|4    With Async/Await and set ConfigureAwait to false              90.657
|-------------------------------------------------------------------------------------

如果您看到结果,则测试#1和测试#2之间没有重大区别,因此包装到async-await不会对您的方案产生影响。

以下是测试#3和#4

的代码

测试3(使用StringBuilder并只写入一次文件)

private static async void DoHashes()
{
    Stopwatch sw = new Stopwatch();
    sw.Start();
    await Task.Run(() =>
    {
        StringBuilder sb = new StringBuilder();
        foreach (var word in File.ReadLines(Input file path))
        {
           sb.AppendLine(MD5Hash(word));
        }

       File.AppendAllText(Output file path, sb.ToString());
    });

    sw.Stop();

    MessageBox.Show("Time Taken by Do Hashes : " + (sw.ElapsedMilliseconds / 1000.0) + " secs");
}

此测试的结果是 0.143 (比测试#1和#2好1000倍),因为多次没有通过进程获取文件句柄。

测试4(使用Async / Await并将ConfigureAwait设置为false)

private static async void DoHashes()
{
    Stopwatch sw = new Stopwatch();
    sw.Start();
    await Task.Run(() =>
    {
        foreach (var word in File.ReadLines(Input file path)
        {
           File.AppendAllText(Output file path, MD5Hash(word) + Environment.NewLine);
        }
    }).ConfigureAwait(false);

    sw.Stop();

    MessageBox.Show("Time Taken by Do Hashes : " + (sw.ElapsedMilliseconds / 1000.0) + " secs");
}

ConfigureAwait尝试设置为 NOT 以将延续编组回到捕获的原始上下文,这样您就可以看到性能提升了 - 与测试#1和测试#2相比它需要 40%更少的时间(仅需90.657秒)。

答案 1 :(得分:0)

根据你所说的你所做的一切都很慢。

采取这个基本案例:

var source = Enumerable.Range(0, 1000000).Select(x => x.ToString()).ToArray();

var sw = Stopwatch.StartNew();
var results = source.Select(x => MD5Hash(x)).ToArray();
sw.Stop();

Console.WriteLine(sw.Elapsed.TotalMilliseconds);

此代码在5316.8091毫秒内完成。这是每毫秒188个哈希值。

如果我将测试放在async方法中并运行:

var results = await Task.Run(() => source.Select(x => MD5Hash(x)).ToArray());

...然后每毫秒需要5531.4172毫秒或大约181个哈希值。

没有async我就跑了:

var results = Task.Run(() => source.Select(x => MD5Hash(x)).ToArray()).Result;

它以5441.0798毫秒或大约184个每毫秒的哈希值完成。

因此,我只能得出结论,您提供的代码并不会导致速度减慢。

现在,如果您想尝试使用Microsoft的Reactive Framework,请尝试更快地运行。然后你可以这样写:

var sw = Stopwatch.StartNew();

var query =
    from n in Observable.Range(0, 1000000)
    from h in Observable.Start(() => MD5Hash(n.ToString()))
    select new { n, h };

query
    .ToArray()
    .Subscribe(xs =>
    {
        var results = xs.OrderBy(x => x.n).Select(x => x.h).ToArray();

        sw.Stop();

        Console.WriteLine(sw.Elapsed.TotalMilliseconds);
    });

在多个后台线程上运行,但可以编组回UI并在5秒内运行。

Just NuGet" System.Reactive"并将using System.Reactive.Linq添加到您的代码中。

答案 2 :(得分:-2)

如果您在散列期间/之后不需要任何表单交互,则不要使用异步。任务就足够了。

private static void DoHashes()
{
    Task.Run(() =>
    {       
        foreach (var word in File.ReadLines("the file path"))
        { 
            File.AppendAllText("the output path",  MD5Hash(word) + Environment.NewLine);      
        }  
    });
}

另外,放置static s