web-dev-qa-db-ja.com

.NETでFileInfo.CopyTo()を使用してファイルコピーの進行状況を表示できますか?

ファイル、ディレクトリ、再帰的なサブディレクトリなどをコピーするc#(.NET 2.0 Framework)でコピーユーティリティを作成しました。プログラムには、コピーされる現在のファイル、現在のファイル番号(シーケンス)、総数を示すGUIがありますコピーするファイルの数とコピー操作の完了率。現在のファイル/合計ファイルに基づいた進行状況バーもあります。

私の問題は、大きなファイルのコピーに関連しています。 (FileInfo.CopyToメソッドを使用する現在のクラス構造を使用して)大きなファイルの合計コピーの進行状況を示す方法を見つけることができませんでした。回避策として、ファイルコピー操作とGUI表示を独自のスレッドに分離し、作業が行われていることを示すビジュアルキューを設定しました。少なくともユーザーは、プログラムが凍結されておらず、ファイルをコピーしていることを認識しています。

合計バイト数に基づいて進行状況を表示したり、現在のファイルからコピーされた合計バイト数を示すFileInfo.CopyToメソッドから発生するイベントの種類があると、より良いでしょう。

私はFileInfo.Lengthプロパティを知っているので、これに基づく独自のイベントMacGuyverがあり、GUI側にハンドラーがあり、更新を読み取る(おそらくFileInfoのチェックに基づく)ハンドラーがあると確信しています。何らかのタイプのタイマーを使用する宛先オブジェクトの長さプロパティ?)。

私が見落としている、これを行う方法を誰かが知っていますか?回避できる場合は、ストリームを介してバイトをコピーし、そのように追跡するようにクラスを書き直したくない(ただし、そのルートに行きたくなっていると思う)。

前もって感謝します

PS-今のところ.NET 2.0フレームワークにこだわっているので、3.0以降でのみ利用可能な機能を必要とするソリューションは、私にとって選択肢ではありません。

PPS-C#だけでなく、あらゆる.NET言語のソリューションを利用できます。

30
Jason Down

FileInfo.CopyToは、基本的には、kernel32.dllのWin32 APIコール「CopyFile」のラッパーです。このメソッドは進行状況コールバックをサポートしていません。

ただし、CopyFileExメソッドはそのため、ここで説明するように、数分で独自の.NETラッパーを書き込むことができます。 http://www.pinvoke.net/default.aspx/kernel32.CopyFileEx

33
Gaspar Nagy

マークされた回答 で提供される実装も使用しました。ただし、次に、.NETから使用するより良い™APIを提供するラッパーを作成しました。

用途:

XCopy.Copy(networkFile.FullPath, temporaryFilename, true, true, (o, pce) => 
{
    worker.ReportProgress(pce.ProgressPercentage, networkFile);
});

実装

/// <summary>
/// PInvoke wrapper for CopyEx
/// http://msdn.Microsoft.com/en-us/library/windows/desktop/aa363852.aspx
/// </summary>
public class XCopy
{
    public static void Copy(string source, string destination, bool overwrite, bool nobuffering)
    {
         new XCopy().CopyInternal(source, destination, overwrite, nobuffering, null);            
    }

    public static void Copy(string source, string destination, bool overwrite, bool nobuffering, EventHandler<ProgressChangedEventArgs> handler)
    {            
         new XCopy().CopyInternal(source, destination, overwrite, nobuffering, handler);            
    }

    private event EventHandler Completed;
    private event EventHandler<ProgressChangedEventArgs> ProgressChanged;

    private int IsCancelled;
    private int FilePercentCompleted;
    private string Source;
    private string Destination;        

    private XCopy()
    {
        IsCancelled = 0;
    }

    private void CopyInternal(string source, string destination, bool overwrite, bool nobuffering, EventHandler<ProgressChangedEventArgs> handler)
    {
        try
        {
            CopyFileFlags copyFileFlags = CopyFileFlags.COPY_FILE_RESTARTABLE;
            if (!overwrite)
                copyFileFlags |= CopyFileFlags.COPY_FILE_FAIL_IF_EXISTS;

            if (nobuffering)
                copyFileFlags |= CopyFileFlags.COPY_FILE_NO_BUFFERING;

            Source = source;
            Destination = destination;

            if (handler != null)
                ProgressChanged += handler;

            bool result = CopyFileEx(Source, Destination, new CopyProgressRoutine(CopyProgressHandler), IntPtr.Zero, ref IsCancelled, copyFileFlags);
            if (!result)
                throw new Win32Exception(Marshal.GetLastWin32Error());
        }
        catch (Exception)
        {
            if (handler != null)
                ProgressChanged -= handler;

            throw;
        }
    }

    private void OnProgressChanged(double percent)
    {
        // only raise an event when progress has changed
        if ((int)percent > FilePercentCompleted)
        {
            FilePercentCompleted = (int)percent;

            var handler = ProgressChanged;
            if (handler != null)
                handler(this, new ProgressChangedEventArgs((int)FilePercentCompleted, null));
        }
    }

    private void OnCompleted()
    {
        var handler = Completed;
        if (handler != null)
            handler(this, EventArgs.Empty);
    }

    #region PInvoke

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool CopyFileEx(string lpExistingFileName, string lpNewFileName, CopyProgressRoutine lpProgressRoutine, IntPtr lpData, ref Int32 pbCancel, CopyFileFlags dwCopyFlags);

    private delegate CopyProgressResult CopyProgressRoutine(long TotalFileSize, long TotalBytesTransferred, long StreamSize, long StreamBytesTransferred, uint dwStreamNumber, CopyProgressCallbackReason dwCallbackReason,
                                                    IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData);

    private enum CopyProgressResult : uint
    {
        PROGRESS_CONTINUE = 0,
        PROGRESS_CANCEL = 1,
        PROGRESS_STOP = 2,
        PROGRESS_QUIET = 3
    }

    private enum CopyProgressCallbackReason : uint
    {
        CALLBACK_CHUNK_FINISHED = 0x00000000,
        CALLBACK_STREAM_SWITCH = 0x00000001
    }

    [Flags]
    private enum CopyFileFlags : uint
    {
        COPY_FILE_FAIL_IF_EXISTS = 0x00000001,
        COPY_FILE_NO_BUFFERING = 0x00001000,
        COPY_FILE_RESTARTABLE = 0x00000002,
        COPY_FILE_OPEN_SOURCE_FOR_WRITE = 0x00000004,
        COPY_FILE_ALLOW_DECRYPTED_DESTINATION = 0x00000008
    }

    private CopyProgressResult CopyProgressHandler(long total, long transferred, long streamSize, long streamByteTrans, uint dwStreamNumber,
                                                   CopyProgressCallbackReason reason, IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData)
    {
        if (reason == CopyProgressCallbackReason.CALLBACK_CHUNK_FINISHED)
            OnProgressChanged((transferred / (double)total) * 100.0);

        if (transferred >= total)
            OnCompleted();

        return CopyProgressResult.PROGRESS_CONTINUE;
    }

    #endregion

}
26
Dennis

私はパーティーに少し遅れていることを知っていますが、CopyFileExを返し、TaskIProgress<double>を受け入れるCancellationTokenのラッパーを作成しました。残念ながら、これは.NET 2.0フレームワークでは機能しませんが、4.5を使用するすべての人にとって、これによりawaitキーワードを使用できます。

public static class FileEx
{
    public static Task CopyAsync(string sourceFileName, string destFileName)
    {
        return CopyAsync(sourceFileName, destFileName, CancellationToken.None);
    }

    public static Task CopyAsync(string sourceFileName, string destFileName, CancellationToken token)
    {
        return CopyAsync(sourceFileName, destFileName, token, null);
    }

    public static Task CopyAsync(string sourceFileName, string destFileName, IProgress<double> progress)
    {
        return CopyAsync(sourceFileName, destFileName, CancellationToken.None, progress);
    }

    public static Task CopyAsync(string sourceFileName, string destFileName, CancellationToken token, IProgress<double> progress)
    {
        int pbCancel = 0;
        CopyProgressRoutine copyProgressHandler;
        if (progress != null)
        {
            copyProgressHandler = (total, transferred, streamSize, streamByteTrans, dwStreamNumber, reason, hSourceFile, hDestinationFile, lpData) =>
            {
                progress.Report((double)transferred / total * 100);
                return CopyProgressResult.PROGRESS_CONTINUE;
            };
        }
        else
        {
            copyProgressHandler = EmptyCopyProgressHandler;
        }
        token.ThrowIfCancellationRequested();
        var ctr = token.Register(() => pbCancel = 1);
        var copyTask = Task.Run(() =>
        {
            try
            {
                CopyFileEx(sourceFileName, destFileName, copyProgressHandler, IntPtr.Zero, ref pbCancel, CopyFileFlags.COPY_FILE_RESTARTABLE);
                token.ThrowIfCancellationRequested();
            }
            finally
            {
                ctr.Dispose();
            }
        }, token);
        return copyTask;
    }

    private static CopyProgressResult EmptyCopyProgressHandler(long total, long transferred, long streamSize, long streamByteTrans, uint dwStreamNumber, CopyProgressCallbackReason reason, IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData)
    {
        return CopyProgressResult.PROGRESS_CONTINUE;
    }

    #region DLL Import

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool CopyFileEx(string lpExistingFileName, string lpNewFileName,
       CopyProgressRoutine lpProgressRoutine, IntPtr lpData, ref Int32 pbCancel,
       CopyFileFlags dwCopyFlags);

    delegate CopyProgressResult CopyProgressRoutine(
        long totalFileSize,
        long totalBytesTransferred,
        long streamSize,
        long streamBytesTransferred,
        uint dwStreamNumber,
        CopyProgressCallbackReason dwCallbackReason,
        IntPtr hSourceFile,
        IntPtr hDestinationFile,
        IntPtr lpData);

    enum CopyProgressResult : uint
    {
        PROGRESS_CONTINUE = 0,
        PROGRESS_CANCEL = 1,
        PROGRESS_STOP = 2,
        PROGRESS_QUIET = 3
    }

    enum CopyProgressCallbackReason : uint
    {
        CALLBACK_CHUNK_FINISHED = 0x00000000,
        CALLBACK_STREAM_SWITCH = 0x00000001
    }

    [Flags]
    enum CopyFileFlags : uint
    {
        COPY_FILE_FAIL_IF_EXISTS = 0x00000001,
        COPY_FILE_RESTARTABLE = 0x00000002,
        COPY_FILE_OPEN_SOURCE_FOR_WRITE = 0x00000004,
        COPY_FILE_ALLOW_DECRYPTED_DESTINATION = 0x00000008
    }

    #endregion
}
9
AJ Richardson

神の愛のために、ストリームを使用して独自のファイルコピーを実装しないでください。 Gasparが言及したWin32 CopyFile API呼び出しは、例えばDMAは、ウィルが書いたコードがドーナツにドルを賭けるのに対して、それを行うには十分に「スマート」ではないでしょう。

CopyFileExが適切に処理します。または、ターゲットファイルの増大するサイズを監視し、その情報を使用して進行状況バーを更新するBackgroundWorkerを実装することもできます。後者の方法はPInvokeを節約しますが、前者はおそらく長期的には少しクリーンです。

6
Coderer

これらの種類については、Shell32にフォールバックしました(または、ShellUIですか?もうわかりません)。これにより、ユーザーがコピー操作の表示に慣れているネイティブのWindowsダイアログが表示されます。それはあなたの既存のダイアログを置き換えるので、それはあなたにとって正しい答えではないかもしれませんが、それらの「ピンチ」シナリオを覚えておくと役に立ちます。

Microsoft.VisualBasic.FileIO.FileSystem.CopyFile(
    srcPath, 
    dstPath, 
    Microsoft.VisualBasic.FileIO.UIOption.AllDialogs,    
    Microsoft.VisualBasic.FileIO.UICancelOption.ThrowException
);

はい、Microsoft.VisualBasicアセンブリを参照する必要があります。私は love このアセンブリに成長しました。

5
cfeduke

CopyFileExメソッドを指摘してくれた@Gasperと@Dennisに感謝します。私は中止コピーでデニスの答えを拡張しました

    /// <summary>
    /// Type indicates how the copy gets completed.
    /// </summary>
    internal enum CopyCompletedType
    {
        Succeeded,
        Aborted,
        Exception
    }

/// <summary>
/// Event arguments for file copy 
/// </summary>
internal class FileCopyEventArgs : EventArgs
{
    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="type">type of the copy completed type enum</param>
    /// <param name="exception">exception if any</param>
    public FileCopyEventArgs(CopyCompletedType type, Exception exception)
    {
        Type = type;
        Exception = exception;
    }

    /// <summary>
    /// Type of the copy completed type
    /// </summary>
    public CopyCompletedType Type
    {
        get;
        private set;

    }

    /// <summary>
    /// Exception if any happend during copy.
    /// </summary>
    public Exception Exception
    {
        get;
        private set;
    }

}

/// <summary>
/// PInvoke wrapper for CopyEx
/// http://msdn.Microsoft.com/en-us/library/windows/desktop/aa363852.aspx
/// </summary>
internal class XCopy
{

    private int IsCancelled;
    private int FilePercentCompleted;

    public XCopy()
    {
        IsCancelled = 0;
    }

    /// <summary>
    /// Copies the file asynchronously
    /// </summary>
    /// <param name="source">the source path</param>
    /// <param name="destination">the destination path</param>
    /// <param name="nobuffering">Bufferig status</param>
    /// <param name="handler">Event handler to do file copy.</param>
    public void CopyAsync(string source, string destination, bool nobuffering)
    {
        try
        {
            //since we needed an async copy ..
            Action action = new Action(
                () => CopyInternal(source, destination, nobuffering)
                    );
            Task task = new Task(action);
            task.Start();
        }
        catch (AggregateException ex)
        {
            //handle the inner exception since exception thrown from task are wrapped in
            //aggreate exception.
            OnCompleted(CopyCompletedType.Exception, ex.InnerException);
        }
        catch (Exception ex)
        {
            OnCompleted(CopyCompletedType.Exception, ex);
        }
    }

    /// <summary>
    /// Event which will notify the subscribers if the copy gets completed
    /// There are three scenarios in which completed event will be thrown when
    /// 1.Copy succeeded
    /// 2.Copy aborted.
    /// 3.Any exception occured.
    /// These information can be obtained from the Event args.
    /// </summary>
    public event EventHandler<FileCopyEventArgs> Completed;
    /// <summary>
    /// Event which will notify the subscribers if there is any progress change while copying.
    /// This will indicate the progress percentage in its event args.
    /// </summary>
    public event EventHandler<ProgressChangedEventArgs> ProgressChanged;

    /// <summary>
    /// Aborts the copy asynchronously and throws Completed event when done.
    /// User may not want to wait for completed event in case of Abort since 
    /// the event will tell that copy has been aborted.
    /// </summary>
    public void AbortCopyAsync()
    {
        Trace.WriteLine("Aborting the copy");
        //setting this will cancel an operation since we pass the
        //reference to copyfileex and it will periodically check for this.
        //otherwise also We can check for iscancelled on onprogresschanged and return 
        //Progress_cancelled .
        IsCancelled = 1;

        Action completedEvent = new Action(() =>
            {
                //wait for some time because we ll not know when IsCancelled is set , at what time windows stops copying.
                //so after sometime this may become valid .
                Thread.Sleep(500);
                //do we need to wait for some time and send completed event.
                OnCompleted(CopyCompletedType.Aborted);
                //reset the value , otherwise if we try to copy again since value is 1 , 
                //it thinks that its aborted and wont allow to copy.
                IsCancelled = 0;
            });

        Task completedTask = new Task(completedEvent);
        completedTask.Start();
    }


    /// <summary>
    /// Copies the file using asynchronos task
    /// </summary>
    /// <param name="source">the source path</param>
    /// <param name="destination">the destination path</param>
    /// <param name="nobuffering">Buffering status</param>
    /// <param name="handler">Delegate to handle Progress changed</param>
    private void CopyInternal(string source, string destination, bool nobuffering)
    {
        CopyFileFlags copyFileFlags = CopyFileFlags.COPY_FILE_RESTARTABLE;

        if (nobuffering)
        {
            copyFileFlags |= CopyFileFlags.COPY_FILE_NO_BUFFERING;
        }

        try
        {
            Trace.WriteLine("File copy started with Source: " + source + " and destination: " + destination);
            //call win32 api.
            bool result = CopyFileEx(source, destination, new CopyProgressRoutine(CopyProgressHandler), IntPtr.Zero, ref IsCancelled, copyFileFlags);
            if (!result)
            {
                //when ever we get the result as false it means some error occured so get the last win 32 error.
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
        }
        catch (Exception ex)
        {
            //the mesage will contain the requested operation was aborted when the file copy
            //was cancelled. so we explicitly check for that and do a graceful exit
            if (ex.Message.Contains("aborted"))
            {
                Trace.WriteLine("Copy aborted.");
            }
            else
            {
                OnCompleted(CopyCompletedType.Exception, ex.InnerException);
            }
        }
    }

    private void OnProgressChanged(double percent)
    {
        // only raise an event when progress has changed
        if ((int)percent > FilePercentCompleted)
        {
            FilePercentCompleted = (int)percent;

            var handler = ProgressChanged;
            if (handler != null)
            {
                handler(this, new ProgressChangedEventArgs((int)FilePercentCompleted, null));
            }
        }
    }

    private void OnCompleted(CopyCompletedType type, Exception exception = null)
    {
        var handler = Completed;
        if (handler != null)
        {
            handler(this, new FileCopyEventArgs(type, exception));
        }
    }

    #region PInvoke

    /// <summary>
    /// Delegate which will be called by Win32 API for progress change
    /// </summary>
    /// <param name="total">the total size</param>
    /// <param name="transferred">the transferrred size</param>
    /// <param name="streamSize">size of the stream</param>
    /// <param name="streamByteTrans"></param>
    /// <param name="dwStreamNumber">stream number</param>
    /// <param name="reason">reason for callback</param>
    /// <param name="hSourceFile">the source file handle</param>
    /// <param name="hDestinationFile">the destination file handle</param>
    /// <param name="lpData">data passed by users</param>
    /// <returns>indicating whether to continue or do somthing else.</returns>
    private CopyProgressResult CopyProgressHandler(long total, long transferred, long streamSize, long streamByteTrans, uint dwStreamNumber,
                                                   CopyProgressCallbackReason reason, IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData)
    {
        //when a chunk is finished call the progress changed.
        if (reason == CopyProgressCallbackReason.CALLBACK_CHUNK_FINISHED)
        {
            OnProgressChanged((transferred / (double)total) * 100.0);
        }

        //transfer completed
        if (transferred >= total)
        {
            if (CloseHandle(hDestinationFile))
            {
                OnCompleted(CopyCompletedType.Succeeded, null);
            }
            else
            {
                OnCompleted(CopyCompletedType.Exception,
                    new System.IO.IOException("Unable to close the file handle"));
            }
        }

        return CopyProgressResult.PROGRESS_CONTINUE;
    }
    [System.Runtime.InteropServices.DllImport("Kernel32")]
    private extern static Boolean CloseHandle(IntPtr handle);

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool CopyFileEx(string lpExistingFileName, string lpNewFileName, CopyProgressRoutine lpProgressRoutine, IntPtr lpData, ref Int32 pbCancel, CopyFileFlags dwCopyFlags);

    private delegate CopyProgressResult CopyProgressRoutine(long TotalFileSize, long TotalBytesTransferred, long StreamSize, long StreamBytesTransferred, uint dwStreamNumber, CopyProgressCallbackReason dwCallbackReason,
                                                    IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData);

    private enum CopyProgressResult : uint
    {
        PROGRESS_CONTINUE = 0,
        PROGRESS_CANCEL = 1,
        PROGRESS_STOP = 2,
        PROGRESS_QUIET = 3
    }

    private enum CopyProgressCallbackReason : uint
    {
        CALLBACK_CHUNK_FINISHED = 0x00000000,
        CALLBACK_STREAM_SWITCH = 0x00000001
    }

    [Flags]
    private enum CopyFileFlags : uint
    {
        COPY_FILE_FAIL_IF_EXISTS = 0x00000001,
        COPY_FILE_NO_BUFFERING = 0x00001000,
        COPY_FILE_RESTARTABLE = 0x00000002,
        COPY_FILE_OPEN_SOURCE_FOR_WRITE = 0x00000004,
        COPY_FILE_ALLOW_DECRYPTED_DESTINATION = 0x00000008
    }

    #endregion

}

クライアントはXCopyクラスのオブジェクトを作成し、copy/abortメソッドを呼び出すことができます。

3
srsyogesh

私と同じように誰かがまだこの問題に遭遇した場合(10年後!)、CopyFileEx関数とMoveFileWithProgress関数の周りにラッパーを作成しました(ここでの回答の一部として)。便利な機能(非同期、アクセス権チェックなど)を備えています。バイトのフォーマット、ディレクトリのコピー...)

ここで確認してください- GitHub および Nuget

1
Martin Ch