我是异步编程的新手,我一直在研究一个小例子来演示如何使用任务进行编程。我想知道你对样本的意见。
我在哪里拦截?我做错了吗? 它可以以某种方式变得更好吗?
以下是示例代码:
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;
}
}
答案 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
)。
我做错了吗?
我能立即发现的事情是:
Task.Run
,而不是将调用封装到“异步”方法中的Task.Run
。这样,你就不会让他认为这种方法当然不是真正的异步。Async
后缀。能以某种方式变得更好吗?
您可以使用自然异步文件读取的API,例如Task.Run
和FileStream
类,而不是使用TextReader
来执行IO绑定操作。这样,你真的暴露了async
方法,而不是“异步过同步”方法。
答案 2 :(得分:1)
我看到它有些不对劲。通常当一个人正在工作async
时,因为他们想要处理在可用时涓涓细流的结果(想想:yield
),而不是等待整个集合完全形成。
ToList
的每次使用都是async
和yield
的一记耳光。实质上,您声明希望代码在进行之前等待。
考虑在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
快得多,而且也不会受到时区转换的任何怪癖的影响。