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

问题描述 投票:0回答:1

我正在为大学教师编写非常简单的 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);
}
c# winforms async-await blocking
1个回答
1
投票

我开始看到 UI 冻结,如果我单击并拖动窗口,它会显示“无响应” 所有工作完成后,UI 响应能力恢复。

在异步 UI 工作中,这通常意味着以下两件事之一:

  1. 异步工作实际上是同步运行的。
  2. UI 受到进度更新的轰炸,以至于无法保持响应。

我在你的代码中没有看到(2)的证据,所以我认为(1)是罪魁祸首。 异步磁盘 I/O 可以在 Windows 上同步运行的原因有很多,例如 NTFS 级加密或压缩。

无论出于何种原因,UI 应用程序都有一个简单的修复方法:将 整个 目录复制逻辑包装在

Task.Run
中。例如,将
await CopyDirectoryAsync(...)
替换为
await Task.Run(() => CopyDirectoryAsync(...))
。然后,您可以删除其他
Task.Run
调用(例如
CreateDirectoryAsync
),并考虑用对
CopyFileAsync
的简单(可能更快)调用替换整个
File.Copy

© www.soinside.com 2019 - 2024. All rights reserved.