我正在为大学教师编写非常简单的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() Package 了Directory. Directory()
private static async Task CreateDirectoryAsync(string path)
{
await Task.Run(() => { Directory.CreateDirectory(path); });
}
所以我的理论是,由于Directory.CreateDirectory()
是严格同步的,即使我用Task.Run()
将它 Package 在异步方法中,它仍然会阻塞WinForms应用程序的消息队列
我相信这同样适用于Directory.GetDirectories()
和Directory.GetFiles()
所以我的问题是
在WinForms应用程序中有没有一种方法可以卸载同步操作,使其不会阻塞UI线程,并且最好不使用Control.BeginInvoke()
。
这个CopyDirectory操作需要很长时间是可以的,因为它可能是SMB/UNIX/WIN32或其他的限制。但为什么UI线程被阻塞。
UPD:
当我在调试器中点击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);
}
1条答案
按热度按时间pbossiut1#
我开始看到UI冻结,如果我点击并拖动一个窗口,它会在所有工作完成后返回“无响应”。
在异步UI工作中,这通常意味着两件事之一:
1.异步工作实际上是同步运行的。
我在你的代码中没有看到(2)的证据,所以我假设(1)是罪魁祸首。还有a number of reasons why asynchronous disk I/O may run synchronously on Windows,如NTFS级加密或压缩。
不管是什么原因,对于UI应用程序有一个简单的修复方法:将 * 整个 * 目录复制逻辑 Package 在
Task.Run
中。例如,将await CopyDirectoryAsync(...)
替换为await Task.Run(() => CopyDirectoryAsync(...))
。然后,您可以删除其他Task.Run
调用(例如CreateDirectoryAsync
),并考虑将整个CopyFileAsync
替换为对File.Copy
的简单(可能更快)调用。