2017-04-04 70 views
1

要异步复制文件,是否会像这样工作?F#异步文件复制

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

    // do other stuff 

    Async.AwaitIAsyncResult task 

特别是,这将启动一个新的线程来做副本,而我“做其他的东西”?

UPDATE:

发现了另一种解决方案:

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 

回答

1

您可以用几个printfns测试它自己。我发现我必须以异步方式运行以强制主线程等待复制完成。我不确定为什么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 
+0

看起来不错!下一个问题:我可以确定线程池在任何给定时间有多大?我想限制我的线程使用,以避免压倒系统。例如如果我复制了很多文件,而且有些文件很大。假设我只想使用5个线程(任意)。我能以某种方式发现吗? – user1443098

+0

.NET任务使用ThreadPool - CLR将管理您的任务并将其安排在池中。如果你想限制你创建的任务的数量,这个C#答案可能会有所帮助:http://stackoverflow.com/questions/2898609/system-threading-tasks-limit-the-number-of-concurrent-tasks –

1

如果你正在试图做的一切是在另一个线程运行的东西,而你做其他事情,那么你的初始Task.Run的方法应该是罚款(请注意,你可以得到如果您拨打Task.Run<_>而不是非通用Task.Run,这可能会稍微容易处理),请致电Task<unit>

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

1

你的问题是合并两个问题,即多线程和asychrony。认识到这些东西是完全不同的概念很重要:

异步是关于一个任务的工作流程,我们可以独立于主程序流程完成这些任务。

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


现在,当涉及到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是返回unitCopyToAsync是返回Task异步方法的采用同步方法不同。你不能通过异步包装来奇迹般地使异步方法成为异步方式,而是需要确保你一直使用异步方式。

+0

将'如果流创建时没有'FileOptions.Asynchronous'标志,CopyToAsync'实际上会执行一个异步拷贝?见例如http://stackoverflow.com/a/35467471/82959。 – kvb

+0

@kvb不确定。很多例子都表明这没问题,但快速查看参考资料来源让我更加怀疑。我已经改变它在安全的一面。 – TheInnerLight

+0

所以这看起来很酷!非常感谢您的解释。你将如何增强它以对IOException执行一定数量的重试? – user1443098