f#的异步目录访问者#

时间:2011-10-29 11:24:11

标签: c# multithreading asynchronous f#

在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#中实现它。

1 个答案:

答案 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 )
    {
        ...
    }
}

当然,MessagesDirectories集合必须是线程安全的。


编辑:实际上,PLINQ使这更容易:

    Parallel.ForEach( dirs, d => Travel( d, nesting - 1, null ) );