2008-10-09 3 views
28

Я создал утилиту копирования в C# (.NET 2.0 Framework), которая копирует файлы, каталоги и рекурсивные подкаталоги и т. Д. Программа имеет графический интерфейс, который показывает текущий файл, который копируется, текущий номер файла (последовательность), общее количество копируемых файлов и процент выполненных операций копирования. Существует также индикатор выполнения, основанный на текущих файлах/файлах.Могу ли я показать прогресс копирования файла, используя FileInfo.CopyTo() в .NET?

Проблема связана с копированием больших файлов. Мне не удалось найти способ указать общий ход копирования большого файла (используя мою текущую структуру классов, которая утилизирует метод FileInfo.CopyTo). В качестве обходного пути я разделил операции копирования файлов и графического интерфейса на свои собственные потоки и настроил визуальный сигнал, чтобы показать, что работа выполняется. По крайней мере, пользователь знает, что программа не была заморожена и все еще копирует файлы.

Было бы лучше продемонстрировать прогресс на основе общего количества байтов или иметь какой-либо тип события, которое запускается из метода FileInfo.CopyTo, который указывает общее количество байт, скопированных из текущего файла.

Мне известно о свойстве FileInfo.Length, поэтому я уверен, что MacGuyver использует мое собственное событие, основанное на этом, и имеет обработчик на стороне GUI вещей, читающих обновления (возможно, на основе проверяя свойство FileInfo.Length объекта назначения с использованием какого-то типа таймера?).

Кто-нибудь знает, как это сделать, что я пропускаю. Если я могу избежать этого, я бы предпочел не переписывать свой класс, чтобы копировать байты через поток и отслеживать его таким образом (хотя я думаю, что мог бы застрять на этом пути).

Заранее спасибо

PS - я застрял в рамках .NET 2.0 в настоящее время, так что любое решение, которое требует функции, доступные в> = 3.0 только не вариант для меня.

PPS - Я открыт для решений на любом языке .NET, но не только C#.

+0

любой полный исходный код образца? – Kiquenet 2011-09-24 16:18:12

ответ

33

FileInfo.CopyTo в основном представляет собой оболочку API-интерфейса Win32 API «CopyFile» в файле kernel32.dll. Этот метод не поддерживает обратный вызов прогресса.

Однако метод CopyFileEx делает, и вы можете написать свой собственный .NET обертку вокруг него в течение нескольких минут, как описано здесь: http://www.pinvoke.net/default.aspx/kernel32.CopyFileEx

+0

Спасибо Гаспар. Этот вариант выглядит как один из возможных способов решения проблемы. Я посмотрю на это немного больше. – 2008-10-09 15:40:15

+1

Для чего это стоит, я успешно использовал этот подход. На самом деле я уверен, что буквально скопировал этот код с pinvoke.net. agentidle, вы можете сделать хороший маленький класс, чтобы обернуть его, а не иметь дело со всеми параметрами. – 2008-10-09 15:44:51

6

За любовь Бога не реализовать свой собственный файл копировать с помощью потоков! API Win32 CopyFile API, упомянутый Гаспар, может использовать, например, DMA, тогда как я бы поставил доллары на пончики, которые написал код, не будет достаточно «умным», чтобы это сделать.

CopyFileEx будет относиться к вам правильно, или вы можете реализовать BackgroundWorker, который следит за ростом размера целевого файла и обновляет индикатор выполнения, используя эту информацию. Последний метод экономит вам PInvoke, но первый, вероятно, немного чист в долгосрочной перспективе.

+0

Просмотр целевого файла с использованием объекта FileInfo и проверка длины - это еще один вариант, который я рассмотрел. Я согласен с тем, что использование подхода CopyFileEx, вероятно, является лучшим способом. – 2008-10-09 18:12:28

5

Для таких вещей я вернулся к Shell32 (или это ShellUI? Я уже не знаю). Это дает вам родное диалоговое окно Windows, которое пользователи используют для операций копирования. Я предполагаю, что это заменит ваш уже существующий диалог, чтобы он не был правильным ответом для вас, но полезно помнить о тех сценариях «в крайнем случае».

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

Да, вы должны обратиться к сборке Microsoft.VisualBasic. Я вырос до love этой сборки.

+0

Единственная причина, по которой я не выбрала этот маршрут, - это то, что мне нужно было убедиться, что пользователь не может отменить операцию копирования. – 2008-10-10 12:35:07

23

Я также использовал реализацию, представленную в marked answer. Однако я создал оболочку для предоставления API-интерфейса nicer ™ для использования с .NET.

Использование:

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 

} 
+0

Это на самом деле довольно сладкое ... – 2011-12-01 13:21:53

+0

Это хорошо. Я на самом деле не использовал его, так как нашел x100 более высокую производительность, используя «WebClient», чтобы загрузить файл async. – Dennis 2011-12-01 16:40:58

3

Благодаря @Gasper и @Dennis за указание метода CopyFileEx. я продлил Дэннис ответ с копией прерывания

/// <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 и вызов копирования/отмену методы.

9

Я знаю, что я немного опоздал на вечеринку, но я сделал обертку для CopyFileEx, который возвращает Task и принимающий CancellationToken и IProgress<double>. К сожалению, он не будет работать в среде .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 
} 

 Смежные вопросы

  • Нет связанных вопросов^_^