Могу ли я показать прогресс копирования файла, используя FileInfo.CopyTo () в .NET?
Вопрос
Я создал утилиту копирования в c # (.NET 2.0 Framework), которая копирует файлы, каталоги, рекурсивные подкаталоги и т. д. В программе есть графический интерфейс, который показывает текущий копируемый файл, текущий номер файла (последовательность), общее количество копируемых файлов и процент выполненных операций копирования. Также имеется индикатор выполнения, основанный на текущем файле / общем количестве файлов.
Моя проблема связана с копированием больших файлов. Я не смог найти способ указать общий прогресс копирования большого файла (используя мою текущую структуру классов, которая использует метод FileInfo.CopyTo). В качестве обходного пути я разделил операции копирования файлов и отображения графического интерфейса для их собственных потоков и настроил визуальную подсказку, чтобы показать, что работа выполняется. По крайней мере, пользователь знает, что программа не заморожена и все еще копирует файлы. Р>
Было бы лучше иметь возможность отображать прогресс на основе общего количества байтов или иметь какой-либо тип события, которое вызывается из метода FileInfo.CopyTo, который указывает общее количество байтов, скопированных из текущего файла. р>
Мне известно о свойстве FileInfo.Length, поэтому я уверен, что есть способ, которым MacGuyver использует мое собственное событие, основанное на этом и имеющее обработчик на стороне графического интерфейса для чтения обновлений (возможно, на основе проверки свойство FileInfo.Length целевого объекта, использующего таймер определенного типа?).
Кто-нибудь знает способ сделать это, что я пропускаю. Если бы я мог избежать этого, я бы предпочел не переписывать свой класс, чтобы копировать байты в потоке и отслеживать его таким образом (хотя я думаю, что, возможно, застрял на этом пути).
Заранее спасибо
PS. Пока я застрял на платформе .NET 2.0, поэтому любое решение, для которого требуются функции, доступные только в > = 3.0, мне не подходит.
PPS - я открыт для решений на любом языке .NET, не только на c #.
Решение
FileInfo.CopyTo - это, по сути, оболочка для вызова Win32 API " CopyFile " в kernel32.dll. Этот метод не поддерживает обратный вызов прогресса.
Однако метод CopyFileEx делает это, и вы можете написать собственную оболочку .NET за несколько минут, как описано здесь: http://www.pinvoke.net/default.aspx/kernel32.CopyFileEx р>
Другие советы
Я также использовал реализацию, представленную в помеченном ответе . Однако затем я создал оболочку, чтобы обеспечить лучшую & # 8482; API для использования из .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
}
Я знаю, что немного опоздал на вечеринку, но я сделал обертку для 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
}
Ради Бога, не создавайте свою собственную копию файла, используя потоки! Вызов Win32 CopyFile API, о котором упоминал Гаспар, может использовать, например, DMA, тогда как я поставил бы доллары на пончики, которые код, который написал Уилл, не был бы & Quot; smart & Quot; достаточно, чтобы сделать это.
CopyFileEx будет обращаться с вами правильно, или вы могли бы реализовать BackgroundWorker, который отслеживает растущий размер целевого файла и обновляет индикатор выполнения, используя эту информацию. Последний метод спасает вас от PInvoke, но в долгосрочной перспективе он, вероятно, немного чище.
Из-за такого рода вещей я вернулся к Shell32 (или это ShellUI? Я больше не знаю). Это дает вам собственный диалог Windows, который пользователи привыкли видеть для операций копирования. Я предполагаю, что он заменит ваш уже существующий диалог, поэтому он может быть неправильным для вас, но его полезно запомнить для этих & Quot; в крайнем случае & Quot; сценарии.
Microsoft.VisualBasic.FileIO.FileSystem.CopyFile(
srcPath,
dstPath,
Microsoft.VisualBasic.FileIO.UIOption.AllDialogs,
Microsoft.VisualBasic.FileIO.UICancelOption.ThrowException
);
Да, вы должны ссылаться на сборку Microsoft.VisualBasic. Я люблю эту сборку.
Спасибо @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 и вызвать метод copy / abort.
Если кто-то все еще сталкивается с этой проблемой (10 лет спустя!), как я, я создал оболочку для функций CopyFileEx и MoveFileWithProgress (как некоторые ответы здесь) с некоторыми дополнительными, полезными функциями (такими как асинхронный доступ, доступ). проверка прав, форматирование байтов, копирование каталогов ...)