2011-10-29 30 views
1

在F#中,我可以使用F#异步目录访问者C#

// 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#版本很差,并且有很多性能问题。

我知道我只是可以在我的C#项目中使用F#代码,但由于这只是练习,我更感兴趣的是如何在C#中完成它。

+0

要创建工作项进入线程池的第5级 - 这就是(根据您的目录设置)远得多,因为所有的开销。您可以尝试C#(CTP)中较新的等待支持或减少嵌套。 – Carsten

+0

@CarstenKönig,是吗?那么F#版本,它工作正常?我的问题是F#知道所有的异步操作何时完成,然后解除主线程的阻塞。 – ebb

回答

2

首先,Travel()中存在一个错误,其中您为每个目录排队工作的线程池。您正在捕获lambda中的d,但在lambda运行时,d可能总是dirs集合中的最后一条路径。下面是该修复:

foreach (var d in dirs) 
{ 
    var d2 = d; 
    ThreadPool.QueueUserWorkItem(o => Travel(d2, nesting - 1, cdown)); 
} 

除此之外,您正在为您的磁盘,这是相当昂贵的每个目录CountdownEvent。事实上,TravelSync中的CountdownEvent是冗余的,因为它同步运行。你可以摆脱他们:

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)); 
+0

啊,当然......使用Tasks或者'Parallel.ForEach'可以不需要使用ThreadPool.QueueUserWorkItem这个我不能跟踪的东西,也没有使用一些讨厌的技巧(比如我的代码示例中的CountdownEvent)。无论如何,感谢您的答案,以及伟大的代码示例! – ebb