我正在为大学教师编写非常简单的 WinForms 应用程序。
教师将能够将文件/文件夹发送到学生计算机(30-35 个工作站)
通过网络进行网络共享。
我已经有工作应用程序,它运行得很好并且适合所有需求,但是..
当我尝试复制包含 25 个子文件夹和 311 个文件总计 7.22 MB 的文件夹时
我开始看到 UI 冻结,如果我单击并拖动窗口,它会显示“未响应”
所有工作完成后,UI 响应能力恢复。
我尝试使用异步而不是同步...
我为所有 30 台计算机创建
List<Task> tasks
并使用 await Task.WhenAll(tasks)
等待它
这是执行目录复制的代码:
private async Task CopyDirectoryAsync(string sourceDirectory, string destinationDirectory, string[] directories,
string[] files, string host, bool isCollectOperation)
{
try
{
if (isCollectOperation)
{
directories = Directory.GetDirectories(sourceDirectory, "*", _defaultEnumerationOptions);
files = Directory.GetFiles(sourceDirectory, "*.*", _defaultEnumerationOptions);
}
await CreateDirectoryAsync(destinationDirectory);
for (var i = 0; i < directories.Length; i++)
{
await CreateDirectoryAsync(@$"{destinationDirectory}{directories[i].Replace(sourceDirectory, "")}");
}
for (var i = 0; i < files.Length; i++)
{
await CopyFileAsync(files[i], $@"{files[i].Replace(sourceDirectory, destinationDirectory)}", null,
false);
}
SetHostLabelColor(host, Color.Green);
}
catch (Exception ex)
{
SetHostLabelColor(host, Color.Red);
await Log(ex);
}
}
文件复制:
private async Task CopyFileAsync(string sourceFile, string destinationFile, string host, bool isFileCopy)
{
try
{
await using var sourceStream = new FileStream(
sourceFile,
FileMode.Open,
FileAccess.Read,
FileShare.Read,
8192,
FileOptions.Asynchronous | FileOptions.SequentialScan);
await using var destinationStream = new FileStream(
destinationFile,
FileMode.OpenOrCreate, FileAccess.Write,
FileShare.None,
8192,
FileOptions.Asynchronous | FileOptions.SequentialScan);
await sourceStream.CopyToAsync(destinationStream);
if (isFileCopy)
{
SetHostLabelColor(host, Color.Green);
}
}
catch (Exception ex)
{
if (!isFileCopy) throw;
SetHostLabelColor(host, Color.Red);
await Log(ex);
}
}
我用 Task.Run() 包装了 Directory.CreateDirectory()
private static async Task CreateDirectoryAsync(string path)
{
await Task.Run(() => { Directory.CreateDirectory(path); });
}
所以我的理论是,由于
Directory.CreateDirectory()
是严格同步的,即使我用 Task.Run()
将其包装在异步方法中 - 它仍然会阻塞 WinForms 应用程序的消息队列Directory.GetDirectories()
和 Directory.GetFiles()
所以我的问题是:
WinForms 应用程序中有没有一种方法可以以不会阻塞 UI 线程的方式卸载同步操作,并且最好不使用
Control.BeginInvoke()
..
这个 CopyDirectory 操作花费很长时间是可以的,因为它可能是 SMB/NTFS/WIN32 或其他的限制..但是为什么 UI 线程被阻塞。
更新:
当我在调试器中点击“Break All”时
它几乎总是位于这段代码中
[LibraryImport("kernel32.dll", EntryPoint = "CreateFileW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
[GeneratedCode("Microsoft.Interop.LibraryImportGenerator", "7.0.8.42427")]
private unsafe static SafeFileHandle CreateFilePrivate(string lpFileName, int dwDesiredAccess, FileShare dwShareMode, SECURITY_ATTRIBUTES* lpSecurityAttributes, FileMode dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile)
{
bool flag = false;
nint handle = 0;
SafeFileHandle safeFileHandle = new SafeFileHandle();
int lastSystemError;
try
{
try
{
fixed (char* ptr = &Utf16StringMarshaller.GetPinnableReference(lpFileName))
{
void* lpFileName2 = ptr;
Marshal.SetLastSystemError(0);
/* Always here --> */ handle = __PInvoke((ushort*)lpFileName2, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
lastSystemError = Marshal.GetLastSystemError();
}
}
finally
{
}
flag = true;
}
finally
{
if (flag)
{
Marshal.InitHandle(safeFileHandle, handle);
}
}
Marshal.SetLastPInvokeError(lastSystemError);
return safeFileHandle;
[DllImport("kernel32.dll", EntryPoint = "CreateFileW", ExactSpelling = true)]
static extern unsafe IntPtr __PInvoke(ushort* lpFileName, int dwDesiredAccess, FileShare dwShareMode, SECURITY_ATTRIBUTES* lpSecurityAttributes, FileMode dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile);
}
我开始看到 UI 冻结,如果我单击并拖动窗口,它会显示“无响应” 所有工作完成后,UI 响应能力恢复。
在异步 UI 工作中,这通常意味着以下两件事之一:
我在你的代码中没有看到(2)的证据,所以我认为(1)是罪魁祸首。 异步磁盘 I/O 可以在 Windows 上同步运行的原因有很多,例如 NTFS 级加密或压缩。
无论出于何种原因,UI 应用程序都有一个简单的修复方法:将 整个 目录复制逻辑包装在
Task.Run
中。例如,将 await CopyDirectoryAsync(...)
替换为 await Task.Run(() => CopyDirectoryAsync(...))
。然后,您可以删除其他 Task.Run
调用(例如 CreateDirectoryAsync
),并考虑用对 CopyFileAsync
的简单(可能更快)调用替换整个 File.Copy
。