F # Асинхронный каталог посетителя c # - PullRequest
1 голос
/ 29 октября 2011

В 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.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 # плохая и имеет много проблем с производительностью.

Я знаю об этомЯ просто могу использовать код F # в своем проекте C #, но так как это только для упражнения, меня больше интересует, как это сделать в C #.

1 Ответ

2 голосов
/ 29 октября 2011

Во-первых, в Travel() есть ошибка, когда вы ставите в очередь работу пула потоков для каждого каталога. Вы захватываете d в лямбде, но ко времени запуска лямбды 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 ) );
...