无法关闭在单独线程上打开的表单
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。问题的代码试图做到这一点,但以各种方式失败了:
Task.Factory.StartNew(...,TaskScheduler.FromCurrentSynchronizationContext()
将 运行 在原始线程上,即 UI 线程。如果原始线程不是 UI,则会出现错误。
ShowDialog()
显示模态窗体,而 在窗体关闭之前不会 return。那么哪个线程将要 运行 并尝试关闭表单?
Invoke
是一个阻塞调用,所以如果 UI 线程已经被阻塞,对 Close
的调用将无限期地阻塞。
看起来问题的代码是试图一个一个地绕过这些错误:
- 因为
ShowDialog
块,使用Task..StartNew
。
- 这会抛出异常,因此在 UI 线程上使用
TaskScheduler.FromCurrentSynchronizationContext()
到 运行。
- 尝试从另一个线程关闭表单
从评论来看,代码似乎已经失败了。无法关闭表单只是另一个症状:
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)
。
要开始上传和显示进度,我们将进度表显示为modeless和Show
,并调用其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);
}
}
我有一个表格可以打开这样的辅助表格:
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。问题的代码试图做到这一点,但以各种方式失败了:
Task.Factory.StartNew(...,TaskScheduler.FromCurrentSynchronizationContext()
将 运行 在原始线程上,即 UI 线程。如果原始线程不是 UI,则会出现错误。ShowDialog()
显示模态窗体,而 在窗体关闭之前不会 return。那么哪个线程将要 运行 并尝试关闭表单?Invoke
是一个阻塞调用,所以如果 UI 线程已经被阻塞,对Close
的调用将无限期地阻塞。
看起来问题的代码是试图一个一个地绕过这些错误:
- 因为
ShowDialog
块,使用Task..StartNew
。 - 这会抛出异常,因此在 UI 线程上使用
TaskScheduler.FromCurrentSynchronizationContext()
到 运行。 - 尝试从另一个线程关闭表单
从评论来看,代码似乎已经失败了。无法关闭表单只是另一个症状:
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)
。
要开始上传和显示进度,我们将进度表显示为modeless和Show
,并调用其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);
}
}