如何在WinForms应用程序中执行同步操作时不阻塞UI线程

sauutmhj  于 2023-10-23  发布在  其他
关注(0)|答案(1)|浏览(132)

我正在为大学教师编写非常简单的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);
}
pbossiut

pbossiut1#

我开始看到UI冻结,如果我点击并拖动一个窗口,它会在所有工作完成后返回“无响应”。
在异步UI工作中,这通常意味着两件事之一:
1.异步工作实际上是同步运行的。

  1. UI被进度更新轰炸到无法保持响应的程度。
    我在你的代码中没有看到(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的简单(可能更快)调用。

相关问题