I'm writing very simplistic WinForms app for college teachers.
Teacher will be able to send files/folders to student computers (30-35 workstations)
over the network to network share. I already have working application and it works pretty well and suits all needs, but..

When I try to copy a folder that contains 25 subfolders, and 311 files total 7.22 MB
I start to see UI freezes and if I click and drag on a window it got to state "Not Responding"
after all work is done UI responsiveness returns.

I tried to use async over sync...
I create List<Task> tasks for all 30 computers and await it with 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); });
 }

So my theory is that since Directory.CreateDirectory() is strictly synchronous even I wrapped it in an asynchronous method with Task.Run() - it still blocks message queue of WinForms app
I believe the same applies to Directory.GetDirectories() and to Directory.GetFiles()

So my question is:
Is there a way in WinForms app to offload a synchronous operation in such a manner that it will not block UI thread, and preferably without using Control.BeginInvoke()..

此CopyDirectory操作花费较长时间是可以的,因为它可能是SMB/NTFS/Win32或其他的限制.但为什么UI线程会被阻塞.

UPD:
When I hit Break All in a debugger it almost always sits in this code

[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工作中,这通常意味着以下两种情况之一:

  1. 异步工作实际上是同步运行的.
  2. 用户界面正在被进度更新轰炸到无法保持响应的地步.

我在您的代码中看不到(2)的证据,所以我认为(1)是罪魁祸首.有a number of reasons why asynchronous disk I/O may run synchronously on Windows种,如NTFS级加密或压缩.

不管是什么原因,对UI应用程序有一个简单的修复:将entire个目录复制逻辑包装在Task.Run中.例如,用await Task.Run(() => CopyDirectoryAsync(...))代替await CopyDirectoryAsync(...).然后,您可以删除其他Task.Run个调用(例如CreateDirectoryAsync),并考虑将整个CopyFileAsync替换为对File.Copy的简单(可能更快)调用.

Csharp相关问答推荐

无法使用ternal- .net修复可空警告

ListaryImportProperty的默认DllImportSearchPathsProperty行为

C#相同名称的枚举方法和normal方法,参数类型不同

在发布表单时绑定包含附加(嵌套)列表的对象列表的正确语法是什么

C#EF Core 8.0表现与预期不符

MongoDB.NET-将数据绑定到模型类,但无法读取整数值

.NET 6控制台应用程序,RabbitMQ消费不工作时,它的程序文件中的S

在C#中,有没有一种方法可以集中定义跨多个方法使用的XML参数描述符?

用C#调用由缓冲区指针参数组成的C API

如何将不同类型的扩展参数的javascript函数转换成C#风格?

在静态模式下实例化配置

当用户右键单击文本框并单击粘贴时触发什么事件?

如何使用EF Core和.NET 8来upsert到具有多对多关系的表?

使用CollectionView时在.NET Maui中显示数据时出现问题

源代码生成器项目使用`dotnet build`编译,而不是在Visual Studio中编译?

C#LINQ延迟执行和嵌套方法

JSON串行化程序问题.SQLite中的空值

在构造函数中传递C#函数以用作EventHandler委托的订阅服务器

获取应用程序版本信息时出现奇怪信息

将带有嵌套If-Else的Foreach循环转换为Linq表达式