在F#我可以使用
// Synchronous version
let rec folderCollectorSync path =
try
let dirs = Directory.GetDirectories path
for z in dirs do folderCollectorSync z
with
| ex -> ()
// Asynchronous version that uses synchronous when 'nesting <= 0'
let rec folderCollector path nesting =
async { if nesting <= 0 then return folderCollectorSync path
else
try
let dirs = Directory.GetDirectories path
do! [for z in dirs -> folderCollector z (nesting - 1) ]
|> Async.Parallel |> Async.Ignore
with ex -> () }
folderCollector @"C:\" 5 |> Async.RunSynchronously
前往前5个级别的目录async。
我尝试重做上面的代码(当然不使用Async.Parallel
)。
看起来像是:
static void TravelSync(string path, CountdownEvent cd)
{
var dirs = Directory.GetDirectories(path);
var cdown = new CountdownEvent(dirs.Length);
foreach (var d in dirs)
TravelSync(d, cdown);
cdown.Wait();
cd.Signal();
}
static void Travel(string path, int nesting, CountdownEvent cd)
{
if (!Directories.Contains(path))
{
if (nesting <= 0)
{
TravelSync(path, cd);
}
else
{
Messages.Add(path);
Directories.Add(path);
var dirs = Directory.GetDirectories(path);
var cdown = new CountdownEvent(dirs.Length);
foreach (var d in dirs)
ThreadPool.QueueUserWorkItem(o => Travel(d, nesting - 1, cdown));
cdown.Wait();
cd.Signal();
}
}
}
毫不奇怪,c#版本很慢,并且在抓取5个目录后它也停止了。
所以我的问题是: F#如何跟踪异步操作?我的C#版本很差,并且有很多性能问题。
我知道我只能在我的C#项目中使用F#代码,但由于这只是为了练习,我更感兴趣的是如何在C#中实现它。
答案 0 :(得分:2)
首先,Travel()
中存在一个错误,您可以在其中为每个目录排队线程池工作。您正在lambda中捕获d
,但是当lambda运行时,d
可能始终是dirs
集合中的最后一个路径。以下是对此的修复:
foreach ( var d in dirs )
{
var d2 = d;
ThreadPool.QueueUserWorkItem( o => Travel( d2, nesting - 1, cdown ) );
}
除此之外,您要为磁盘上的每个目录创建一个CountdownEvent
,这非常昂贵。实际上,CountdownEvent
中的TravelSync
是多余的,因为它同步运行。你可以摆脱它们:
static void TravelSync(string path, CountdownEvent cd)
{
var dirs = Directory.GetDirectories(path);
//var cdown = new CountdownEvent(dirs.Length);
// this is normal synchronous code
foreach (var d in dirs)
TravelSync(d, null);
//cdown.Wait();
if ( cd != null ) cd.Signal();
}
如果您使用的是.NET 4.0,则可以使用Travel()
清理Tasks
:
...
else
{
Messages.Add( path );
Directories.Add( path );
try
{
var dirs = Directory.GetDirectories( path );
var tasks = dirs.Select(
d => Task.Factory.StartNew(
() => Travel( d, nesting - 1, null )
)
).ToArray();
Task.WaitAll( tasks );
foreach ( var t in tasks ) t.Dispose();
}
catch ( Exception x )
{
...
}
}
当然,Messages
和Directories
集合必须是线程安全的。
编辑:实际上,PLINQ使这更容易:
Parallel.ForEach( dirs, d => Travel( d, nesting - 1, null ) );