【问题标题】:F# Async File CopyF# 异步文件复制
【发布时间】:2017-08-29 18:40:27
【问题描述】:

要异步复制文件,这样的方法可行吗?

let filecopyasync (source, target) =
    let task = Task.Run((fun () ->File.Copy(source, target, true)))

    // do other stuff

    Async.AwaitIAsyncResult task

特别是,这会在我“做其他事情”时启动一个新线程来做副本吗?

更新:

找到另一个解决方案:

let asyncFileCopy (source, target, overwrite) =
    printfn "Copying %s to %s" source target
    let fn = new Func<string * string * bool, unit>(File.Copy)
    Async.FromBeginEnd((source, target, overwrite), fn.BeginInvoke, fn.EndInvoke)

let copyfile1 = asyncFileCopy("file1", "file2", true)
let copyfile2 = asyncFileCopy("file3", "file4", true)

[copyfile1; copyfile2] |> seq |>  Async.Parallel |> Async.RunSynchronously |> ignore

【问题讨论】:

    标签: asynchronous f#


    【解决方案1】:

    您可以使用一些 printfns 自行测试。我发现我必须 RunAsynchronously 强制主线程等待复制完成。我不确定为什么 await 不起作用,但您可以看到预期的一组输出,表明副本发生在后台。

    open System
    open System.IO
    open System.Threading
    open System.Threading.Tasks
    let filecopyasync (source, target) =
        let task = Task.Run((fun () ->
              printfn "CopyThread: %d" Thread.CurrentThread.ManagedThreadId; 
              Thread.Sleep(10000);  
              File.Copy(source, target, true); printfn "copydone"))
    
        printfn "mainThread: %d" Thread.CurrentThread.ManagedThreadId;
        let result=Async.AwaitIAsyncResult task 
        Thread.Sleep(3000)
        printfn "doing stuff"
        Async.RunSynchronously result
        printfn "done"
    

    输出:

    filecopyasync (@"foo.txt",@"bar.txt");;
    mainThread: 1
    CopyThread: 7
    doing stuff
    copydone
    done
    

    【讨论】:

    • 看起来不错!下一个问题:我可以在任何给定时间确定线程池有多大吗?我想限制我的线程使用以避免压倒系统。例如如果我要复制很多文件并且有些文件很大。假设我只想使用 5 个线程(任意)。我能以某种方式找到它吗?
    • .NET 任务使用线程池——CLR 将管理您的任务并将它们安排在池中。如果您想限制您创建的任务数量,这个 C# 答案可能会有所帮助:stackoverflow.com/questions/2898609/…
    【解决方案2】:

    如果您要做的只是在执行其他操作时在另一个线程上运行某些操作,那么您最初的 Task.Run 方法应该没问题(请注意,如果您改为调用 Task.Run&lt;_&gt;,您可以获得 Task&lt;unit&gt;非通用的Task.Run,这可能更容易处理)。

    但是,您应该清楚自己的目标 - 可以说,“正确的”异步文件副本不需要单独的 .NET 线程(这是一个相对重量级的原语),并且会依赖于完成端口等操作系统功能反而;因为System.IO.File 没有提供您需要自己编写的本机CopyAsync 方法(请参阅https://stackoverflow.com/a/35467471/82959 了解易于音译的简单C# 实现)。

    【讨论】:

      【解决方案3】:

      您的问题将两个问题混为一谈,即多线程和异步。重要的是要认识到这些东西是完全不同的概念:

      异步是关于任务的工作流程,我们独立于主程序流程来响应这些任务的完成。

      多线程是一种执行模型,可用于实现异步,尽管异步可以通过其他方式实现(例如硬件中断)。


      现在,谈到 I/O,您应该问的问题是“我可以启动另一个线程来为我做这件事吗?”

      你问为什么?

      如果你在主线程中做一些 I/O,你通常会阻塞主线程等待结果。如果你通过创建一个新线程来逃避这个问题,你实际上并没有解决这个问题,你只是移动了它。现在您已经阻止了您创建的新线程或线程池线程。天哪,同样的问题。

      线程是一种昂贵且宝贵的资源,不应该浪费在等待阻塞 I/O 完成上。

      那么,真正的解决方案是什么?

      嗯,我们通过其中一种方法实现异步。这样,我们可以请求操作系统执行一些 I/O,并请求它让我们知道 I/O 操作何时完成。这样,当我们等待结果时,线程不会被阻塞。在 Windows 中,这是通过称为 I/O 完成端口的东西实现的。


      如何在 F# 中执行此操作?

      .NET CopyToAsync 方法可能是最简单的方法。由于这会返回一个简单的任务,因此创建一个辅助方法会很有帮助:

      type Async with
          static member AwaitPlainTask (task : Task) =
              task.ContinueWith(ignore) |> Async.AwaitTask
      

      然后

      [<Literal>]
      let DEFAULT_BUFFER_SIZE = 4096
      
      let copyToAsync source dest =
          async {
              use sourceFile = new FileStream(source, FileMode.Open, FileAccess.Read, FileShare.Read, DEFAULT_BUFFER_SIZE, true);
              use destFile = new FileStream(dest, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None, DEFAULT_BUFFER_SIZE, true);
              do! sourceFile.CopyToAsync(destFile) |> Async.AwaitPlainTask
          }
      

      然后您可以将其与Async.Parallel 一起使用以同时执行多个副本。

      注意:这与您上面写的不同,因为File.Copy 是一个返回unit 的同步方法,而CopyToAsync 是一个返回Task 的异步方法。你不能通过在它们周围放置异步包装器来神奇地使同步方法异步,相反你需要确保你一直使用异步。

      【讨论】:

      • 如果流是在没有FileOptions.Asynchronous 标志的情况下创建的,CopyToAsync 会实际执行异步复制吗?参见例如stackoverflow.com/a/35467471/82959.
      • @kvb 不确定。很多例子表明这是可以的,但快速浏览参考源会让我更加怀疑。为了安全起见,我已将其更改。
      • 所以这看起来很酷!也非常感谢您的解释。您将如何增强它以对 IOException 进行给定次数的重试?
      • @user1443098 类似于此答案中的RetryRun 函数:stackoverflow.com/a/9218869/5438433,那么您可以例如RetryRun 5 (copyToAsync source dest)
      • @user1443098 不过,也许为了更实际和更强大的使用,您需要考虑可能导致此类异常的原因以及如何通过重试来从它们中恢复。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多