UI 在操作完成之前无响应

UI unresponsive until action is complete

我不确定标题是否很好地描述了这个问题。基本上我拥有的是一个 WinForm 应用程序,它从文件夹中检索文件列表到 ListView,然后单击一个按钮通过 FTP 将它们上传到远程服务器。

从功能上讲,该应用程序按预期运行:

  1. 打开应用程序
  2. 查看 ListView 控件中的文件列表
  3. 点击上传按钮
  4. ListView中列出的文件已上传;每次成功上传后,ListView 都会更新以显示 'Success'
  5. 所有文件上传完毕后,操作停止。

我的问题是,在单击上传按钮后,UI 在操作完成之前几乎没有响应。 ListView 在上传每个文件时按预期更新,甚至使活动行保持焦点。这是处理文件的 for 循环。一些背景知识 - 在下面的代码摘录中,每个 for...loop 处理 2 个文件 - 主文件是唯一显示在 ListView 中的文件。每个循环中的第二个文件是一个触发文件,在发送其主要文件后发送,即:.primary、.trigger。两个文件都必须发送才能注册成功。如果主文件没有相应的触发文件,则无法在 ListView 中上传。

foreach (ListViewItem item in lvSourceFiles.Items)
{
    int rowIndex = item.Index;
    string fileName = item.SubItems[2].Text;

    lvSourceFiles.EnsureVisible(rowIndex);

    transferStatus = "Failed"; // Set this as a default

    // Transfer the source file first
    transferResult = session.PutFiles(readyFile, destFile, false, transferOptions);

    // Throw on any error
    transferResult.Check();

    // If the source file transfer was successful, then transfer the trigger file
    if (transferResult.IsSuccess)
    {
        transferResult = session.PutFiles(triggerFile, destFile, false, transferOptions);
        transferResult.Check();

        if (transferResult.IsSuccess)
        {
            transferStatus = "Success";
        }
    }

    UpdateResultsToListView(lvSourceFiles, rowIndex, fileName, transferStatus);
}

在这种情况下我需要实现某种异步功能,还是有更好的方法可以做到这一点 UI 不会在上传过程中冻结?本质上,我希望能够在上传 运行 时与表单进行交互,例如使用取消按钮来停止上传。就目前而言,在作业完成或我终止应用程序之前,我无法对表单执行任何操作。

谢谢, 詹姆斯

您可以使用 async/await 和方便的 Task.Run 方法将 long-running 操作卸载到 ThreadPool 线程:

transferResult = await Task.Run(() => session.PutFiles(readyFile, destFile, false, transferOptions));

...和:

transferResult = await Task.Run(() => session.PutFiles(triggerFile, destFile, false, transferOptions));

您还应该添加 async modifier in the event handler, in order to enable the await 运算符。

重要提示: 避免在卸载方法中做任何 UI 相关的事情。如果你想在运行过程中与UI进行通信,比如for progress reporting, use the Progress<T> class.

您不能在 GUI 线程上进行冗长的操作。在后台线程上执行它们。

@Theodor 的回答正确地表明您可以将 PutFiles 移动到线程池。

另一种选择是将所有上传逻辑移至线程池并使用 Control.Invoke 回调主线程,仅用于 GUI 更新。

有关完整示例,请参阅 WinSCP 文章 Displaying FTP/SFTP transfer progress on WinForms ProgressBar

选择更适合您的选项。我相信对于没有线程编程经验的人来说,我的方法更容易掌握。