C# 在这个异步示例中,我是否做错了什么?

C# 在这个异步示例中,我是否做错了什么?,c#,asynchronous,task-parallel-library,async-await,C#,Asynchronous,Task Parallel Library,Async Await,我是异步编程新手,我一直在编写一个小示例来演示如何使用任务编程。我想知道你对样品的看法 我在什么地方堵车吗?我做错什么了吗? 能不能让它变得更好 以下是示例的代码: static void Main(string[] args) { const string directory= "files"; IEnumerable<string> files = FindFiles(directory).ToList(); DateTime startAsink =

我是异步编程新手,我一直在编写一个小示例来演示如何使用任务编程。我想知道你对样品的看法

我在什么地方堵车吗?我做错什么了吗? 能不能让它变得更好

以下是示例的代码:

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;
    }
}
static void Main(字符串[]args)
{
const string directory=“files”;
IEnumerable files=FindFiles(directory.ToList();
DateTime startAsink=DateTime.Now;
ProcessFileAsync(文件).ContinueWith(r=>
{
r、 Result.ToList().ForEach(Console.WriteLine);
Console.WriteLine(DateTime.Now.Ticks-startAsink.Ticks);
});
Console.ReadKey();
}
私有静态IEnumerable FindFile(字符串目录)
{
返回Directory.GetFiles(Directory.ToList();
}
私有静态任务ProcessOneFile(字符串名称)
{
返回任务。运行(()=>
{
IEnumerable lines=File.ReadLines(名称);
整数和=0;
foreach(行中的var行)
{
总和+=行分割('')。长度;
}
返回新元组(名称、总和);
});
}
专用静态异步任务ProcessFileAsync(IEnumerable文件)
{
var listOfResults=files.Select(ProcessOneFile);
var task=(wait task.WhenAll(listOfResults)).ToList();
返回任务;
}
}

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

    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);
    }
static void Main(字符串[]args)
{
const string directory=“files”;
IEnumerable files=FindFiles(directory.ToList();
秒表计时=新秒表();
chrono.Start();
var tasks=files.Select(f=>ProcessOneFileAsync(f)).ToArray();
Task.WaitAll(任务);
计时停止();
foreach(任务中的var t)
{
控制台写入线(t.Result);
}
控制台写入线(时间延迟毫秒);
Console.ReadKey();
}
私有静态IEnumerable FindFile(字符串目录)
{
返回Directory.GetFiles(directoy.ToList();
}
专用静态异步任务ProcessOneFileAsync(字符串名称)
{
整数和=0;
使用(TextReader文件=file.OpenText(名称))
{
字符串行=null;
而((line=wait file.readlinesync())!=null)
{
总和+=行分割('')。长度;
}
}
返回新元组(名称、总和);
}
PS:为了获得一致的计时结果,我用秒表代替了日期时间

我在什么地方堵车吗

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

我做错什么了吗

我能马上发现的是:

  • 你用这个。不要在“异步”方法中封装对
    Task.Run
    的调用,而应该公开一个同步方法,让调用方显式调用
    Task.Run
    。这样,你就不会让他假设这个方法是自然异步的,而实际上它不是
  • 命名约定。任何异步方法都应该以
    async
    postfix结尾
  • 能不能让它变得更好


    您可以使用公开自然异步文件读取的API,例如
    FileStream
    TextReader
    类,而不是使用
    Task.Run
    执行IO绑定操作。这样,您实际上公开了一个
    async
    方法,而不是一个“async-over-sync”方法。

    我发现它有几个问题。通常,当一个人使用
    async
    时,这是因为他们希望在结果可用时处理这些结果(想想:
    yield
    ),而不是等待整个集合完全形成

    每次使用
    ToList
    都是对
    async
    yield
    的一记耳光。实际上,您是在声明希望代码在继续之前等待

    考虑在
    FindFiles
    方法中使用
    GetFiles
    。如果省略
    ToList
    GetFiles
    将返回一个
    字符串[]
    ,该字符串无论如何都实现了
    IEnumerable
    。但更重要的是,您应该将
    GetFiles
    完全替换为
    EnumerateFiles
    。我欢迎您在闲暇时阅读MSDN,但我确实引用了以下内容:

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

    因此
    FindFiles
    变成:

    private static IEnumerable<string> FindFiles(string directory)
    {
        return Directory.EnumerateFiles(directory);
    }
    
    私有静态IEnumerable FindFile(字符串目录)
    {
    返回目录。枚举文件(目录);
    }
    
    出于同样的原因,您还应该删除对
    ToList
    的所有引用。这是直接处理
    async
    的两个最大问题。这样,您的代码确实可以执行
    async
    ,并在项目可用时进行处理


    另一件事是,
    DateTime.UtcNow
    应该用于任何内部计时。不仅是
    UtcNow
    比本地化的
    DateTime快得多。现在
    ,它也不受时区转换的任何限制。

    processfileasync
    是异步的。你不用等待。resharper/VS将警告您这一点。你刚才所做的就是开火然后忘记。我不知道你需要什么,但我只是注意到了你。也。您可以使用
    ContinueWith
    ,而您应该使用'await(如果您确实使用异步await方法)来告诉我们