无法关闭在单独线程上打开的表单

Cannot close form opened on a separate thread

我有一个表格可以打开这样的辅助表格:

var progressForm = new ProgressForm();
Task.Factory.StartNew(() =>
{
    progressForm.ShowDialog();
}, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());

我还有关闭表单的扩展方法:

private static void CloseForm(this Form formToClose)
{
    if (formToClose != null)
    {
        formToClose.Invoke((MethodInvoker)delegate { formToClose.Close(); });
    }
}

public static void DoSomething(Form formToClose = null)
{
    formToClose.CloseForm();
}

但是表格没有被关闭。

我错过了什么?

没有操作系统允许后台线程修改UI。问题的代码试图做到这一点,但以各种方式失败了:

  1. Task.Factory.StartNew(...,TaskScheduler.FromCurrentSynchronizationContext() 将 运行 在原始线程上,即 UI 线程。如果原始线程不是 UI,则会出现错误。
  2. ShowDialog() 显示模态窗体,而 在窗体关闭之前不会 return。那么哪个线程将要 运行 并尝试关闭表单?
  3. Invoke 是一个阻塞调用,所以如果 UI 线程已经被阻塞,对 Close 的调用将无限期地阻塞。

看起来问题的代码是试图一个一个地绕过这些错误:

  1. 因为ShowDialog块,使用Task..StartNew
  2. 这会抛出异常,因此在 UI 线程上使用 TaskScheduler.FromCurrentSynchronizationContext() 到 运行。
  3. 尝试从另一个线程关闭表单

从评论来看,代码似乎已经失败了。无法关闭表单只是另一个症状:

I have an AggregrateException being thrown. So I want to first close the progress form and then open an error form (in DoSomething)

报告进度

不过使用进度和取消要容易得多。文章 Enabling Progress and Cancellation in Async APIs. 展示了如何使用 Windows 表单示例执行此操作。

IProgres< T> interface can be used to report progress from a background thread. The built-in implementation, Progress< T> 将转发并处理报告调用到创建它的线程,例如 UI 线程

借用文章的例子,假设有一个 long-运行ning UploadPicturesAsync 异步方法:

async Task<int> UploadPicturesAsync(List<Image> imageList, IProgress<int> progress)
{
            int totalCount = imageList.Count;
            int processCount = await Task.Run<int>(() =>
            {
                int tempCount = 0;
                foreach (var image in imageList)
                {
                    //await the processing and uploading logic here
                    int processed = await UploadAndProcessAsync(image);
                    if (progress != null)
                    {
                        progress.Report((tempCount * 100 / totalCount));
                    }
                    tempCount++;
                }

                return tempCount;
            });
            return processCount;
}

处理是在 Task.Run 开始的后台任务中完成的。每处理一个文件,就调用progress.Report传一个百分比。该方法本身对任何进度条或表单一无所知。

要显示进度,假设我们有一个带有进度条的 ProgressForm 和一个设置进度条值的方法 Report(int)

要开始上传显示进度,我们将进度表显示为modelessShow,并调用其Report 方法来自 Progress class 的处理程序:

private async void Start_Button_Click(object sender, RoutedEventArgs e)
{
    //Display a modeless progress fom
    var progressForm=new ProgressForm();
    progressForm.Show();
    
    //Create the Progress class and pass `ProgressForm.Report` to it
    var progressIndicator = new Progress<int>(pct=>progressForm.Report(pct));
        
    try
    {
        //Execute the background operation
        int uploads=await UploadPicturesAsync(GenerateTestImages(), progressIndicator);

        //Close the form
        progressForm.Close();
        MessageBox.Show("Finished!");
    }
    catch(Exception exc)
    {
  
        progressForm.Close();        
        MessageBox.Show(exc.ToString(), "Failed!", 
                        MessageBoxButtons.OK,MessageBoxIcon.Error);
    }
}