高效移动文件
Efficiently moving a file
我试图将文件从一个目录移动到另一个目录,同时在我的 WPF 应用程序中看到进度条。
移动操作出奇地慢,我找不到让它运行得更快的解决方案(测试速度是 2:30 分钟移动 38 Mb)但我不知道如何移动它有效率的。我移动的方式现在有效,但效率低得可怕。
public delegate void ProgressChangeDelegate(double percentage);
public delegate void CompleteDelegate();
class FileMover
{
public string SourceFilePath { get; set; }
public string DestFilePath { get; set; }
public event ProgressChangeDelegate OnProgressChanged;
public event CompleteDelegate OnComplete;
public FileMover(string Source, string Dest)
{
SourceFilePath = Source;
DestFilePath = Dest;
OnProgressChanged += delegate { };
OnComplete += delegate { };
}
public void Copy()
{
byte[] buffer = new byte[1024 * 1024]; // 1MB buffer
using (FileStream source = new FileStream(SourceFilePath, FileMode.Open, FileAccess.Read))
{
long fileLength = source.Length;
using (FileStream dest = new FileStream(DestFilePath, FileMode.CreateNew, FileAccess.Write))
{
long totalBytes = 0;
int currentBlockSize = 0;
while ((currentBlockSize = source.Read(buffer, 0, buffer.Length)) > 0)
{
totalBytes += currentBlockSize;
double percentage = (double) totalBytes * 100.0 / fileLength;
dest.Write(buffer, 0, currentBlockSize);
OnProgressChanged(percentage);
}
}
}
OnComplete();
}
}
private async void MoveFile(string source, string outDir)
{
if (!string.IsNullOrEmpty(outDir) && !string.IsNullOrEmpty(source))
{
//InputButtonText.Text = "Please be patient while we move your file.";
//Task.Run(() => { new FileInfo(source).MoveTo(Path.Combine(outDir, Path.GetFileName(source))); }).GetAwaiter().OnCompleted(
// () =>
// {
// OutputScanned.ItemsSource = null;
// InputButtonText.Text = "Click to select a file";
// });
var mover = new FileMover(source, Path.Combine(outDir, Path.GetFileName(source)));
await Task.Run(() => { mover.Copy(); });
mover.OnProgressChanged += percentage =>
{
MoveProgress.Value = percentage;
InputButtonText.Text = percentage.ToString();
};
mover.OnComplete += () => { File.Delete(source); };
}
}
The move operation is insanely slow, and I cant find a solution to make it go any faster
移动文件需要这么长时间的原因可能有很多。一些示例:反恶意软件应用程序 - 可能会扫描文件、网络负载(如果移动到另一个 volume/drive)、文件大小本身,以及可能的代码气味。
我的猜测是我认为你按照你对代码所做的方式进行了处理,这样你就可以处理到目前为止已经移动了多少,这很好,但是有替代品 可以移动这些文件的速度更快。
几个选项
- System.IO.File.Move() method - this works well, but you don't have control on the progress either. Under the hood 它实际上调用了:
Win32Native.MoveFile
c++ 函数,效果很好。
- FileInfo.MoveTo - 这最终也将它的工作委托给了
Win32.MoveFile
。
- 你的方式 - 使用
Kernel32.dll
中的一些功能 - 这允许完全控制,进展顺利等...
我会在一分钟内回到上面这些,因为我想根据你最初发布的关于进度没有更早更新的内容来谈谈。
这里的调用 await Task.Run(() => { mover.Copy(); });
将等待完成,但您在此之后注册事件,例如:mover.OnProgressChanged += percentage =>
是在 Copy()
调用之后,所以不,您不会得到任何改变。
即使您收到更改,您仍然会遇到异常,因为您不在 UI 线程上,而是在另一个线程上。例如:
mover.OnProgressChanged += percentage =>
{
MoveProgress.Value = percentage;
InputButtonText.Text = percentage.ToString();
};
您正在尝试从另一个线程更新 UI (progressbar.value),您根本无法这样做。要解决这个问题,您需要从 Dispatcher
调用。例如:
Application.Current.Dispatcher.Invoke(() =>
{
pbProgress.Value = percentage;
});
回档操作
老实说,您仍然可以按照自己的方式做自己想做的事,只需移动一些东西就可以了。否则,下面我写了一个class,你可以在其中使用它来移动文件,报告进度等。请看下面。
注意:我测试了一个 500MB 的文件,它在 2.78 秒内移动,一个 850MB 的文件在 3.37 秒内从本地驱动器移动到另一个卷。
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Transactions; // must add reference to System.Transactions
public class FileHelper
{
#region | Public Events |
/// <summary>
/// Occurs when any progress changes occur with file.
/// </summary>
public event ProgressChangeDelegate OnProgressChanged;
/// <summary>
/// Occurs when file process has been completed.
/// </summary>
public event OnCompleteDelegate OnComplete;
#endregion
#region | Enums |
[Flags]
enum MoveFileFlags : uint
{
MOVE_FILE_REPLACE_EXISTSING = 0x00000001,
MOVE_FILE_COPY_ALLOWED = 0x00000002,
MOVE_FILE_DELAY_UNTIL_REBOOT = 0x00000004,
MOVE_FILE_WRITE_THROUGH = 0x00000008,
MOVE_FILE_CREATE_HARDLINK = 0x00000010,
MOVE_FILE_FAIL_IF_NOT_TRACKABLE = 0x00000020
}
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
}
#endregion
#region | Delegates |
private delegate CopyProgressResult CopyProgressRoutine(
long TotalFileSize,
long TotalBytesTransferred,
long StreamSize,
long StreamBytesTransferred,
uint dwStreamNumber,
CopyProgressCallbackReason dwCallbackReason,
IntPtr hSourceFile,
IntPtr hDestinationFile,
IntPtr lpData);
public delegate void ProgressChangeDelegate(double percentage);
public delegate void OnCompleteDelegate(bool completed);
#endregion
#region | Imports |
[DllImport("Kernel32.dll")]
private static extern bool CloseHandle(IntPtr handle);
[DllImport("Kernel32.dll")]
private static extern bool MoveFileTransactedW([MarshalAs(UnmanagedType.LPWStr)]string existingfile, [MarshalAs(UnmanagedType.LPWStr)]string newfile,
IntPtr progress, IntPtr lpData, IntPtr flags, IntPtr transaction);
[DllImport("Kernel32.dll")]
private static extern bool MoveFileWithProgressA(string existingfile, string newfile,
CopyProgressRoutine progressRoutine, IntPtr lpData, MoveFileFlags flags);
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("79427A2B-F895-40e0-BE79-B57DC82ED231")]
private interface IKernelTransaction
{
void GetHandle([Out] out IntPtr handle);
}
#endregion
#region | Public Routines |
/// <summary>
/// Will attempt to move a file using a transaction, if successful then the source file will be deleted.
/// </summary>
/// <param name="existingFile"></param>
/// <param name="newFile"></param>
/// <returns></returns>
public static bool MoveFileTransacted(string existingFile, string newFile)
{
bool success = true;
using (TransactionScope tx = new TransactionScope())
{
if (Transaction.Current != null)
{
IKernelTransaction kt = (IKernelTransaction)TransactionInterop.GetDtcTransaction(Transaction.Current);
IntPtr txh;
kt.GetHandle(out txh);
if (txh == IntPtr.Zero) { success = false; return success; }
success = MoveFileTransactedW(existingFile, newFile, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, txh);
if (success)
{
tx.Complete();
}
CloseHandle(txh);
}
else
{
try
{
File.Move(existingFile, newFile);
return success;
}
catch (Exception ex) { success = false; }
}
return success;
}
}
/// <summary>
/// Attempts to move a file from one destination to another. If it succeeds, then the source
/// file is deleted after successful move.
/// </summary>
/// <param name="fileToMove"></param>
/// <param name="newFilePath"></param>
/// <returns></returns>
public async Task<bool> MoveFileAsyncWithProgress(string fileToMove, string newFilePath)
{
bool success = false;
try
{
await Task.Run(() =>
{
success = MoveFileWithProgressA(fileToMove, newFilePath, new CopyProgressRoutine(CopyProgressHandler), IntPtr.Zero, MoveFileFlags .MOVE_FILE_REPLACE_EXISTSING|MoveFileFlags.MOVE_FILE_WRITE_THROUGH|MoveFileFlags.MOVE_FILE_COPY_ALLOWED);
});
}
catch (Exception ex)
{
success = false;
}
finally
{
OnComplete(success);
}
return success;
}
private CopyProgressResult CopyProgressHandler(long total, long transferred, long streamSize, long StreamByteTrans, uint dwStreamNumber,CopyProgressCallbackReason reason, IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData)
{
double percentage = transferred * 100.0 / total;
OnProgressChanged(percentage);
return CopyProgressResult.PROGRESS_CONTINUE;
}
#endregion
}
使用方法
一个例子 -
// Just a public property to hold an instance we need
public FileHelper FileHelper { get; set; }
加载时注册事件...
FileHelper = new FileHelper();
FileHelper.OnProgressChanged += FileHelper_OnProgressChanged;
FileHelper.OnComplete += FileHelper_OnComplete;
这是逻辑...
private async void Button_Click(object sender, RoutedEventArgs e)
{
bool success = await FileHelper.MoveFileAsyncWithProgress("FILETOMOVE", "DestinationFilePath");
}
// This is called when progress changes, if file is small, it
// may not even hit this.
private void FileHelper_OnProgressChanged(double percentage)
{
Application.Current.Dispatcher.Invoke(() =>
{
pbProgress.Value = percentage;
});
}
// This is called after a move, whether it succeeds or not
private void FileHelper_OnComplete(bool completed)
{
Application.Current.Dispatcher.Invoke(() =>
{
MessageBox.Show("File process succeded: " + completed.ToString());
});
}
*注意:那个助手class、MoveFileTransacted
中还有另一个函数,你真的不需要这个,这是另一个允许你使用事务移动文件的助手函数;如果发生异常,文件不会移动等...
我试图将文件从一个目录移动到另一个目录,同时在我的 WPF 应用程序中看到进度条。
移动操作出奇地慢,我找不到让它运行得更快的解决方案(测试速度是 2:30 分钟移动 38 Mb)但我不知道如何移动它有效率的。我移动的方式现在有效,但效率低得可怕。
public delegate void ProgressChangeDelegate(double percentage);
public delegate void CompleteDelegate();
class FileMover
{
public string SourceFilePath { get; set; }
public string DestFilePath { get; set; }
public event ProgressChangeDelegate OnProgressChanged;
public event CompleteDelegate OnComplete;
public FileMover(string Source, string Dest)
{
SourceFilePath = Source;
DestFilePath = Dest;
OnProgressChanged += delegate { };
OnComplete += delegate { };
}
public void Copy()
{
byte[] buffer = new byte[1024 * 1024]; // 1MB buffer
using (FileStream source = new FileStream(SourceFilePath, FileMode.Open, FileAccess.Read))
{
long fileLength = source.Length;
using (FileStream dest = new FileStream(DestFilePath, FileMode.CreateNew, FileAccess.Write))
{
long totalBytes = 0;
int currentBlockSize = 0;
while ((currentBlockSize = source.Read(buffer, 0, buffer.Length)) > 0)
{
totalBytes += currentBlockSize;
double percentage = (double) totalBytes * 100.0 / fileLength;
dest.Write(buffer, 0, currentBlockSize);
OnProgressChanged(percentage);
}
}
}
OnComplete();
}
}
private async void MoveFile(string source, string outDir)
{
if (!string.IsNullOrEmpty(outDir) && !string.IsNullOrEmpty(source))
{
//InputButtonText.Text = "Please be patient while we move your file.";
//Task.Run(() => { new FileInfo(source).MoveTo(Path.Combine(outDir, Path.GetFileName(source))); }).GetAwaiter().OnCompleted(
// () =>
// {
// OutputScanned.ItemsSource = null;
// InputButtonText.Text = "Click to select a file";
// });
var mover = new FileMover(source, Path.Combine(outDir, Path.GetFileName(source)));
await Task.Run(() => { mover.Copy(); });
mover.OnProgressChanged += percentage =>
{
MoveProgress.Value = percentage;
InputButtonText.Text = percentage.ToString();
};
mover.OnComplete += () => { File.Delete(source); };
}
}
The move operation is insanely slow, and I cant find a solution to make it go any faster
移动文件需要这么长时间的原因可能有很多。一些示例:反恶意软件应用程序 - 可能会扫描文件、网络负载(如果移动到另一个 volume/drive)、文件大小本身,以及可能的代码气味。
我的猜测是我认为你按照你对代码所做的方式进行了处理,这样你就可以处理到目前为止已经移动了多少,这很好,但是有替代品 可以移动这些文件的速度更快。
几个选项
- System.IO.File.Move() method - this works well, but you don't have control on the progress either. Under the hood 它实际上调用了:
Win32Native.MoveFile
c++ 函数,效果很好。 - FileInfo.MoveTo - 这最终也将它的工作委托给了
Win32.MoveFile
。 - 你的方式 - 使用
Kernel32.dll
中的一些功能 - 这允许完全控制,进展顺利等...
我会在一分钟内回到上面这些,因为我想根据你最初发布的关于进度没有更早更新的内容来谈谈。
这里的调用 await Task.Run(() => { mover.Copy(); });
将等待完成,但您在此之后注册事件,例如:mover.OnProgressChanged += percentage =>
是在 Copy()
调用之后,所以不,您不会得到任何改变。
即使您收到更改,您仍然会遇到异常,因为您不在 UI 线程上,而是在另一个线程上。例如:
mover.OnProgressChanged += percentage =>
{
MoveProgress.Value = percentage;
InputButtonText.Text = percentage.ToString();
};
您正在尝试从另一个线程更新 UI (progressbar.value),您根本无法这样做。要解决这个问题,您需要从 Dispatcher
调用。例如:
Application.Current.Dispatcher.Invoke(() =>
{
pbProgress.Value = percentage;
});
回档操作
老实说,您仍然可以按照自己的方式做自己想做的事,只需移动一些东西就可以了。否则,下面我写了一个class,你可以在其中使用它来移动文件,报告进度等。请看下面。
注意:我测试了一个 500MB 的文件,它在 2.78 秒内移动,一个 850MB 的文件在 3.37 秒内从本地驱动器移动到另一个卷。
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Transactions; // must add reference to System.Transactions
public class FileHelper
{
#region | Public Events |
/// <summary>
/// Occurs when any progress changes occur with file.
/// </summary>
public event ProgressChangeDelegate OnProgressChanged;
/// <summary>
/// Occurs when file process has been completed.
/// </summary>
public event OnCompleteDelegate OnComplete;
#endregion
#region | Enums |
[Flags]
enum MoveFileFlags : uint
{
MOVE_FILE_REPLACE_EXISTSING = 0x00000001,
MOVE_FILE_COPY_ALLOWED = 0x00000002,
MOVE_FILE_DELAY_UNTIL_REBOOT = 0x00000004,
MOVE_FILE_WRITE_THROUGH = 0x00000008,
MOVE_FILE_CREATE_HARDLINK = 0x00000010,
MOVE_FILE_FAIL_IF_NOT_TRACKABLE = 0x00000020
}
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
}
#endregion
#region | Delegates |
private delegate CopyProgressResult CopyProgressRoutine(
long TotalFileSize,
long TotalBytesTransferred,
long StreamSize,
long StreamBytesTransferred,
uint dwStreamNumber,
CopyProgressCallbackReason dwCallbackReason,
IntPtr hSourceFile,
IntPtr hDestinationFile,
IntPtr lpData);
public delegate void ProgressChangeDelegate(double percentage);
public delegate void OnCompleteDelegate(bool completed);
#endregion
#region | Imports |
[DllImport("Kernel32.dll")]
private static extern bool CloseHandle(IntPtr handle);
[DllImport("Kernel32.dll")]
private static extern bool MoveFileTransactedW([MarshalAs(UnmanagedType.LPWStr)]string existingfile, [MarshalAs(UnmanagedType.LPWStr)]string newfile,
IntPtr progress, IntPtr lpData, IntPtr flags, IntPtr transaction);
[DllImport("Kernel32.dll")]
private static extern bool MoveFileWithProgressA(string existingfile, string newfile,
CopyProgressRoutine progressRoutine, IntPtr lpData, MoveFileFlags flags);
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("79427A2B-F895-40e0-BE79-B57DC82ED231")]
private interface IKernelTransaction
{
void GetHandle([Out] out IntPtr handle);
}
#endregion
#region | Public Routines |
/// <summary>
/// Will attempt to move a file using a transaction, if successful then the source file will be deleted.
/// </summary>
/// <param name="existingFile"></param>
/// <param name="newFile"></param>
/// <returns></returns>
public static bool MoveFileTransacted(string existingFile, string newFile)
{
bool success = true;
using (TransactionScope tx = new TransactionScope())
{
if (Transaction.Current != null)
{
IKernelTransaction kt = (IKernelTransaction)TransactionInterop.GetDtcTransaction(Transaction.Current);
IntPtr txh;
kt.GetHandle(out txh);
if (txh == IntPtr.Zero) { success = false; return success; }
success = MoveFileTransactedW(existingFile, newFile, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, txh);
if (success)
{
tx.Complete();
}
CloseHandle(txh);
}
else
{
try
{
File.Move(existingFile, newFile);
return success;
}
catch (Exception ex) { success = false; }
}
return success;
}
}
/// <summary>
/// Attempts to move a file from one destination to another. If it succeeds, then the source
/// file is deleted after successful move.
/// </summary>
/// <param name="fileToMove"></param>
/// <param name="newFilePath"></param>
/// <returns></returns>
public async Task<bool> MoveFileAsyncWithProgress(string fileToMove, string newFilePath)
{
bool success = false;
try
{
await Task.Run(() =>
{
success = MoveFileWithProgressA(fileToMove, newFilePath, new CopyProgressRoutine(CopyProgressHandler), IntPtr.Zero, MoveFileFlags .MOVE_FILE_REPLACE_EXISTSING|MoveFileFlags.MOVE_FILE_WRITE_THROUGH|MoveFileFlags.MOVE_FILE_COPY_ALLOWED);
});
}
catch (Exception ex)
{
success = false;
}
finally
{
OnComplete(success);
}
return success;
}
private CopyProgressResult CopyProgressHandler(long total, long transferred, long streamSize, long StreamByteTrans, uint dwStreamNumber,CopyProgressCallbackReason reason, IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData)
{
double percentage = transferred * 100.0 / total;
OnProgressChanged(percentage);
return CopyProgressResult.PROGRESS_CONTINUE;
}
#endregion
}
使用方法
一个例子 -
// Just a public property to hold an instance we need
public FileHelper FileHelper { get; set; }
加载时注册事件...
FileHelper = new FileHelper();
FileHelper.OnProgressChanged += FileHelper_OnProgressChanged;
FileHelper.OnComplete += FileHelper_OnComplete;
这是逻辑...
private async void Button_Click(object sender, RoutedEventArgs e)
{
bool success = await FileHelper.MoveFileAsyncWithProgress("FILETOMOVE", "DestinationFilePath");
}
// This is called when progress changes, if file is small, it
// may not even hit this.
private void FileHelper_OnProgressChanged(double percentage)
{
Application.Current.Dispatcher.Invoke(() =>
{
pbProgress.Value = percentage;
});
}
// This is called after a move, whether it succeeds or not
private void FileHelper_OnComplete(bool completed)
{
Application.Current.Dispatcher.Invoke(() =>
{
MessageBox.Show("File process succeded: " + completed.ToString());
});
}
*注意:那个助手class、MoveFileTransacted
中还有另一个函数,你真的不需要这个,这是另一个允许你使用事务移动文件的助手函数;如果发生异常,文件不会移动等...