2017-04-04 69 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