web-dev-qa-db-ja.com

C#での非同期ファイルコピー/移動

C#でファイルのコピー/移動を非同期で行う正しい方法は何ですか?

61
user95883

非同期プログラミングの考え方は、async IOが完了すると、呼び出しスレッドがスレッドプールに戻って他のタスクで使用できるようにスレッドプールに戻ることを許可することです。コンテキストはデータ構造に詰め込まれ、1つ以上のIO完了スレッドが完了を待っている呼び出しを監視します。IOが完了すると、完了スレッドがスレッドに戻ります。呼び出しコンテキストを復元するプール:この方法では、100個のスレッドがブロックされる代わりに、完了スレッドと、ほとんどアイドル状態のスレッドプールスレッドがいくつかあります。

私が思いつくことができる最高のものは:

public async Task CopyFileAsync(string sourcePath, string destinationPath)
{
  using (Stream source = File.Open(sourcePath))
  {
    using(Stream destination = File.Create(destinationPath))
    {
      await source.CopyToAsync(destination);
    }
  }
}

ただし、これに関する広範なパフォーマンステストは行っていません。私は少し心配しているのは、もしそれが単純だったら、それはすでにコアライブラリにあるからです。

私が舞台裏で説明していることを待っています。それがどのように機能するかの一般的なアイデアを取得したい場合は、おそらくジェフ・リヒターのAsyncEnumeratorを理解するのに役立つでしょう。それらは完全に同じラインではないかもしれませんが、アイデアは本当に近いです。 「非同期」メソッドからコールスタックを見ると、MoveNextが表示されます。

移動する限り、コピーしてから削除するのではなく、実際に「移動」である場合、非同期である必要はありません。移動は、ファイルテーブルに対する高速アトミック操作です。ただし、ファイルを別のパーティションに移動しようとしない限り、その方法でのみ機能します。

44
csaam

非同期ファイルコピーメソッドは、読み取りと書き込みのデータをプリフェッチして書き込みの準備ができるように、読み取りと書き込みを順番に行っているというOSヒントをOSに提供します。

public static async Task CopyFileAsync(string sourceFile, string destinationFile)
{
    using (var sourceStream = new FileStream(sourceFile, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, FileOptions.Asynchronous | FileOptions.SequentialScan))
    using (var destinationStream = new FileStream(destinationFile, FileMode.CreateNew, FileAccess.Write, FileShare.None, 4096, FileOptions.Asynchronous | FileOptions.SequentialScan))
        await sourceStream.CopyToAsync(destinationStream);
}

バッファサイズも試してみてください。これは4096バイトです。

25
Drew Noakes

@DrewNoakesによってコードを少し強化しました(パフォーマンスとキャンセル):

  public static async Task CopyFileAsync(string sourceFile, string destinationFile, CancellationToken cancellationToken)
  {
     var fileOptions = FileOptions.Asynchronous | FileOptions.SequentialScan;
     var bufferSize = 4096;

     using (var sourceStream = 
           new FileStream(sourceFile, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, fileOptions))

     using (var destinationStream = 
           new FileStream(destinationFile, FileMode.CreateNew, FileAccess.Write, FileShare.None, bufferSize, fileOptions))

        await sourceStream.CopyToAsync(destinationStream, bufferSize, cancellationToken)
                                   .ConfigureAwait(continueOnCapturedContext: false);
  }
11
GregC

非同期デリゲートを使用できます

public class AsyncFileCopier
    {
        public delegate void FileCopyDelegate(string sourceFile, string destFile);

        public static void AsynFileCopy(string sourceFile, string destFile)
        {
            FileCopyDelegate del = new FileCopyDelegate(FileCopy);
            IAsyncResult result = del.BeginInvoke(sourceFile, destFile, CallBackAfterFileCopied, null);
        }

        public static void FileCopy(string sourceFile, string destFile)
        { 
            // Code to copy the file
        }

        public static void CallBackAfterFileCopied(IAsyncResult result)
        {
            // Code to be run after file copy is done
        }
    }

次のように呼び出すことができます。

AsyncFileCopier.AsynFileCopy("abc.txt", "xyz.txt");

この link は、asynコーディングのさまざまなテクニックを示しています

6
Rashmi Pandit

_Task.Run_を避けたい状況もありますが、Task.Run(() => File.Move(source, dest)は機能します。ファイルを同じディスク/ボリューム内で単純に移動する場合、ヘッダーは変更されますがファイルの内容は移動されないため、ほとんど瞬時の操作であるため、考慮する価値があります。さまざまな「純粋な」非同期メソッドは、これを行う必要がない場合でも常にストリームをコピーするため、実際にはかなり遅くなる可能性があります。

6
Casey

this 記事が示唆するようにそれを行うことができます:

public static void CopyStreamToStream(
    Stream source, Stream destination,
    Action<Stream, Stream, Exception> completed)
    {
        byte[] buffer = new byte[0x1000];
        AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(null);

        Action<Exception> done = e =>
        {
            if(completed != null) asyncOp.Post(delegate
                {
                    completed(source, destination, e);
                }, null);
        };

        AsyncCallback rc = null;
        rc = readResult =>
        {
            try
            {
                int read = source.EndRead(readResult);
                if(read > 0)
                {
                    destination.BeginWrite(buffer, 0, read, writeResult =>
                    {
                        try
                        {
                            destination.EndWrite(writeResult);
                            source.BeginRead(
                                buffer, 0, buffer.Length, rc, null);
                        }
                        catch(Exception exc) { done(exc); }
                    }, null);
                }
                else done(null);
            }
            catch(Exception exc) { done(exc); }
        };

        source.BeginRead(buffer, 0, buffer.Length, rc, null);
5
Pablo Retyk

知る限り、ファイルをコピーするための高レベルの非同期APIはありません。ただし、Stream.BeginRead/EndReadおよびStream.BeginWrite/EndWrite AP​​Iを使用して、独自のAPIを構築してそのタスクを実行できます。別の方法として、ここでの回答に記載されているBeginInvoke/EndInvokeメソッドを使用することもできますが、非同期I/Oをブロックしないことを忘れないでください。彼らは単に別のスレッドでタスクを実行します。