我在这个异步示例中做错了吗?

时间:2015-03-15 11:17:39

标签: c# asynchronous task-parallel-library async-await

我是异步编程的新手,我一直在研究一个小例子来演示如何使用任务进行编程。我想知道你对样本的意见。

我在哪里拦截?我做错了吗? 它可以以某种方式变得更好吗?

以下是示例代码:

static void Main(string[] args)
{
    const string directory= "files";

    IEnumerable<string> files = FindFiles(directory).ToList();

    DateTime startAsink = DateTime.Now;

    ProcessFilesAsync(files).ContinueWith(r =>
    {
        r.Result.ToList().ForEach(Console.WriteLine);
        Console.WriteLine(DateTime.Now.Ticks - startAsink.Ticks);
    });

    Console.ReadKey();
}

private static IEnumerable<string> FindFiles(string directoy)
{
    return Directory.GetFiles(directory).ToList();
}

private static Task<Tuple<string, int>> ProcessOneFile(string name)
{
    return Task.Run(() =>
    {
        IEnumerable<string> lines = File.ReadLines(name);
        int sum = 0;

        foreach (var line in lines )
        {
            sum += line.Split(' ').Length;
        }

        return new Tuple<string, int>(name, sum);
    });
}

    private static async Task<IEnumerable<Tuple<string, int>>>  ProcessFilesAsync(IEnumerable<string> files)
    {
        var listOfResults = files.Select(ProcessOneFile);

        var task = (await Task.WhenAll(listOfResults)).ToList();

        return task;
    }
}

3 个答案:

答案 0 :(得分:2)

以下是您的代码的一种可能的异步重构:

    static void Main(string[] args)
    {
        const string directory = "files";
        IEnumerable<string> files = FindFiles(directory).ToList();
        Stopwatch chrono = new Stopwatch();
        chrono.Start();
        var tasks = files.Select(f => ProcessOneFileAsync(f)).ToArray();
        Task.WaitAll(tasks);
        chrono.Stop();
        foreach (var t in tasks)
        {
            Console.WriteLine(t.Result);
        }
        Console.WriteLine(chrono.ElapsedMilliseconds);
        Console.ReadKey();
    }

    private static IEnumerable<string> FindFiles(string directoy)
    {
        return Directory.GetFiles(directoy).ToList();
    }

    private static async Task<Tuple<string, int>> ProcessOneFileAsync(string name)
    {
        int sum = 0;
        using (TextReader file = File.OpenText(name))
        {
            String line = null;
            while ((line = await file.ReadLineAsync()) != null)
            {
                sum += line.Split(' ').Length;
            }
        }
        return new Tuple<string, int>(name, sum);
    }

PS:我将DateTime替换为Stopwatch以获得一致的时序结果。

答案 1 :(得分:2)

  

我是否在任何地方阻止?

不,似乎您使用“异步一路”流程是正确的。 但是,当您通过File.ReadLines使用其async兄弟时,您正在使用阻止API(例如TextReader.ReadLineAsync)。

  

我做错了吗?

我能立即发现的事情是:

  1. 您使用async over sync anti-pattern。您应该公开一个同步的,并让调用者显式调用Task.Run,而不是将调用封装到“异步”方法中的Task.Run。这样,你就不会让他认为这种方法当然不是真正的异步。
  2. 命名约定。任何异步方法都应以Async后缀。
  3. 结尾
      

    能以某种方式变得更好吗?

    您可以使用自然异步文件读取的API,例如Task.RunFileStream类,而不是使用TextReader来执行IO绑定操作。这样,你真的暴露了async方法,而不是“异步过同步”方法。

答案 2 :(得分:1)

我看到它有些不对劲。通常当一个人正在工作async时,因为他们想要处理在可用时涓涓细流的结果(想想:yield),而不是等待整个集合完全形成。

ToList的每次使用都是asyncyield的一记耳光。实质上,您声明希望代码在进行之前等待。

考虑在GetFiles方法中使用FindFiles。如果省略ToList,则GetFiles会返回string[],无论如何都会返回IEnumerable<string>。但更重要的是,您应该将GetFiles替换为EnumerateFiles。我欢迎您在自己的闲暇时阅读 MSDN ,但我确实包含了这句话:

  

EnumerateFiles和GetFiles方法的区别如下:当你   使用EnumerateFiles,您可以开始枚举名称集合   在整个收藏品归还之前;当你使用GetFiles时,你   必须等待返回整个名称数组才能返回   访问数组。因此,当您使用许多文件和   目录,EnumerateFiles可以更有效。

所以FindFiles变为:

private static IEnumerable<string> FindFiles(string directory)
{
    return Directory.EnumerateFiles(directory);
}

出于同样的原因,您还应该删除其他地方对ToList的所有引用。这是直接涉及async的两大问题。这样,您的代码确实可以执行async并处理可用的项目。

另一件事是DateTime.UtcNow应该用于任何内部时间。 UtcNow不仅比本地化的DateTime.Now快得多,而且也不会受到时区转换的任何怪癖的影响。