【问题标题】:Is there an async version of DirectoryInfo.GetFiles / Directory.GetDirectories in dotNet?dotNet 中是否有 DirectoryInfo.GetFiles / Directory.GetDirectories 的异步版本?
【发布时间】:2009-04-05 14:40:19
【问题描述】:

dotNet 中是否有 DirectoryInfo.GetFiles / Directory.GetDirectories 的异步版本?我想在 F# 异步块中使用它们,并且有一个可以用 AsyncCallbacks 调用的版本会很好。

问题是我试图吸入一堆目录,可能是在通过慢速网络连接的 SMB 挂载上,我不希望一堆线程池线程在他们可以做其他事情时等待网络读取工作。

【问题讨论】:

    标签: .net f# asynchronous directoryinfo async-workflow


    【解决方案1】:

    不,我认为没有。池线程方法可能是最实用的。或者,我想您可以直接使用 P/Invoke - 但这将是很多更多的工作。

    【讨论】:

    • 现在 .NET 4.5 推出了一堆新的异步 API,情况仍然如此吗?我在找这个,没看到。
    • .NET Core 2.2 仍然是这种情况。
    • “或者,我想你可以下拉到 P/Invoke” - 我看不到任何用于文件系统操作的 Win32 Overlapped IO 或本机 Win32 异步 API,所以我不确定 P/ Invoke 在这里会有所帮助。据我所知,UWP 中的异步文件系统 API 也在内部使用线程,而不是实际重叠 IO 或 IO 中断驱动。
    【解决方案2】:

    实际上,根据help for Directory.GetFilesDirectory.EnumerateFiles会立即返回第一个结果(是IEnumerable),而不是等待整个列表才返回。我相信这可能就是您想要的。

    【讨论】:

    • 并非如此。即使返回第一个文件也可能需要几十或几百毫秒(想想 UNC 路径)。你的执行线程一直被阻塞。
    • 枚举驱动器超过 5 分钟。
    【解决方案3】:

    这可能被认为是一种 hack,但您可以考虑使用 UWP StorageFolder API

    C# 示例(尽管 F# 可能同样简单):

    using Windows.Storage;
    
    ...
    
    var folder = await StorageFolder.GetFolderFromPathAsync(path);
    var files = await folder.GetFilesAsync();
    var folders = await folder.GetFoldersAsync();
    

    通过使用 Lucian Wischik(Microsoft)的 UWP for Desktop 库,您可以轻松地从传统的 .NET 桌面和控制台应用程序中使用这些。

    Install-Package UwpDesktop
    

    【讨论】:

    • 在内部,这个库使用什么 API?它是调用 true-async (overlapped IO?) Win32 函数,还是只是将所有内容包装在一个线程中?
    • 这是正确的答案,因为它不是假装与线程异步,而是实际上挂钩到硬件/软件中的真实中断。
    • 看起来“UWP 桌面版”经受住了时间的考验。可能有更现代的方法来 p/invoke UWP API。也许 .NET 5 会有所帮助。 :)
    【解决方案4】:

    我没有找到 GetFiles 的异步版本,但是如果您查看其他异步操作的源代码,它们的定义如下:

    module FileExtensions =
    
            let UnblockViaNewThread f =
                async { //let ctxt = System.Threading.SynchronizationContext.Current
                        do! Async.SwitchToNewThread ()
                        let res = f()
                        do! Async.SwitchToThreadPool ()
                        //do! Async.SwitchTo ctxt
                        return res }
    
            type System.IO.File with
                static member AsyncOpenText(path)   = UnblockViaNewThread (fun () -> System.IO.File.OpenText(path))
                static member AsyncAppendText(path) = UnblockViaNewThread (fun () -> System.IO.File.AppendText(path))
                static member AsyncOpenRead(path)   = UnblockViaNewThread (fun () -> System.IO.File.OpenRead(path))
                static member AsyncOpenWrite(path)  = UnblockViaNewThread (fun () -> System.IO.File.OpenWrite(path))
                static member AsyncOpen(path,mode,?access,?share) =
                    let access = match access with Some v -> v | None -> System.IO.FileAccess.ReadWrite
                    let share = match share with Some v -> v | None -> System.IO.FileShare.None
                    UnblockViaNewThread (fun () -> System.IO.File.Open(path,mode,access,share))
    
                static member OpenTextAsync(path)   = System.IO.File.AsyncOpenText(path)
                static member AppendTextAsync(path) = System.IO.File.AsyncAppendText(path)
                static member OpenReadAsync(path)   = System.IO.File.AsyncOpenRead(path)
                static member OpenWriteAsync(path)  = System.IO.File.AsyncOpenWrite(path)
                static member OpenAsync(path,mode,?access,?share) = System.IO.File.AsyncOpen(path, mode, ?access=access, ?share=share)
    

    换句话说,Async 文件、streamreader 和 WebClient 操作只是同步操作的包装器,因此您应该能够编写自己的 GetFiles/GetDirectories 包装器,如下所示:

    module IOExtensions =
        type System.IO.Directory with
            static member AsyncGetFiles(directory) = async { return System.IO.Directory.GetFiles(directory) }
            static member AsyncGetDirectories(path) = async { return System.IO.Directory.GetDirectories(path) }
    

    【讨论】:

    • 这当然不是真正的异步 I/O,但它仍然是一个很好的实用解决方案。如果您确实想要真正的异步操作,则必须使用一些可怕的 Win32 互操作,我认为这在上下文中是不值得的...
    • 另外,这些天我通过 Xamarin 在 iOS 上使用 F#(很快在 Android 上),所以一些可怕的 Win32 互操作甚至是不可能的。
    【解决方案5】:

    我已经多次使用这种方法从函数/过程中获取 Async 对象,而且效果一直很好:

    
    let AsyncGetDirectories path = 
        let fn = new Func<_, _>(System.IO.Directory.GetDirectories)
        Async.BuildPrimitive(path, fn.BeginInvoke, fn.EndInvoke)
    

    【讨论】:

      【解决方案6】:

      我不是 F# 程序员,但我会在 C# 中执行此操作:

      static IEnumerable<string> IterateFiles(string path, string pattern) {
          var entryQueue = new Queue<string>();
          entryQueue.Enqueue(path);
      
          while (entryQueue.Count > 0) {
              var subdirs = Directory.GetDirectories(entryQueue.Peek());
              var files = Directory.GetFiles(entryQueue.Peek(), pattern, SearchOption.TopDirectoryOnly);
              foreach (var file in files)
                  yield return file;
              entryQueue.Dequeue();
      
              foreach(var subdir in subdirs)
                  entryQueue.Enqueue(subdir);
          }
      }
      

      我假设 F# 中存在与迭代器类似的构造。

      【讨论】:

      • 这是 lazy,而不是 async
      • 好吧,把它塞进一个异步方法/任务中,你有什么。通过简单地将所有内容推到不同的线程上是实现异步恕我直言的不好方法。是的,它是并行运行的,但是很难以一种确定的方式终止。
      • 在异步方法/任务中填充它是不一样的,并且与懒惰无关。我同意“将其推入不同的线程”是“糟糕的异步”,它应该使用 IOCP。
      • 令人着迷的是,有几个答案提出了这种解决方案,尽管它与解决所述问题无关。有趣的是,C# 错误答案比 F# 中的等效错误答案要长得多:-)。
      【解决方案7】:

      公主的回答是在任务之间增加粒度的方法——所以这种事情会让其他玩家使用线程池:

      let! x = OpenTextAsync("whatever");
      // opening for something else to run
      let! x = OpenTextAsync("whatever");
      // opening for something else to run
      let! x = OpenTextAsync("whatever");
      

      当这些阻塞调用中的每一个都很重时,它并没有多大帮助 - 而 SMB 上的 GetFiles 几乎就是 heavy 的定义。

      我希望 BeginRead/EndRead 对于目录有某种等价物,而 GetFiles/GetDirectories 只是一个很好的包装器,它围绕暴露了一些异步变体的低级调用。类似BeginReadDir/EndReadDir

      【讨论】:

      • 我找不到公主的答案。
      • @ScottHutchinson 没了,不知道什么时候删除的。 (我的问题来自 2009)。我认为这与 Juliet 的基本相同,指出很多异步操作符并没有很好的支持——它们会消耗线程并且不会对等待句柄做一些明智的事情。
      • 我猜公主把她的名字改成了朱丽叶,她的资料说她是“大祭司”,有点像公主。
      • 不,“女祭司”是一个宗教头衔,而公主则是皇室成员,这是……嘿,我们跑题了!
      猜你喜欢
      • 1970-01-01
      • 2020-04-18
      • 2016-07-22
      • 2018-06-10
      • 2012-03-21
      • 2011-08-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多