【发布时间】:2016-01-09 03:10:30
【问题描述】:
我有一个 WinForms 程序,它下载然后转换一组文件,每个文件都通过一个批处理线程在各自的线程上进行,用于报告总体进度和工作完成情况。每个文件将在其自己的线程上下载,然后一旦下载,它将通过主线程的第二个批处理线程传递到自己的转换线程。通过主线程进行这种传递的原因是,一旦下载了第一个文件,主线程就可以提示用户为所有这些文件的转换版本提供保存位置。
我遇到的问题是 OpenFileDialog.ShowDialog() 方法(用于从用户获取保存目录)阻止了第一个完成的下载线程的主线程回调。这反过来又允许其他下载线程完成(不是问题),但随后也开始执行他们对主线程的回调,此时他们都点击 OpenFileDialog.ShowDialog() 否则他们不会像我的第二个批处理线程那样做在第一次转换时运行,因此它们将能够被添加到批处理线程管理器中。
我正在使用 Dispatcher.CurrentDispatcher.BeginInvoke(myCallbackDelegate, DispatcherPriority.Normal, myParameter) 以便在项目工作完成时对主线程进行回调。我在下面创建了一个简化示例来演示问题代码。
public partial class TestForm : Form
{
private readonly Dispatcher dispatcher = Dispatcher.CurrentDispatcher;
//An instance of my own BatchBackgroundWorkerThread<T> class; this class parrarlelizes tasks and can report their individual and overall progress, completion and cancellation.
private readonly BatchBackgroundWorkerThread<int> conversionBatchBackgroundWorker = new BatchBackgroundWorkerThread<int>();
public TestForm()
{
InitializeComponent();
for (int threadIndex = 0; threadIndex < 4; threadIndex++)
{
new Thread(DoSomeBackgroundWork).Start(threadIndex);
Thread.Sleep(10);
}
}
private void DoSomeBackgroundWork(object threadIndex)
{
Thread.Sleep(1000);
dispatcher.BeginInvoke(new ParameterizedThreadStart(WorkCompletedCallback), DispatcherPriority.Normal, threadIndex);
}
private void WorkCompletedCallback(object threadIndex)
{
//Waits for CanQueueNewItems to be in a valid readable state.
conversionBatchBackgroundWorker.WaitForPendingRun();
//CanQueueNewItems is true when the batch background worker's batch thread has been launched and its internal CountdownEvent and cancellationState have been initialized.
if (conversionBatchBackgroundWorker.IsBusy && conversionBatchBackgroundWorker.CanQueueNewItems)
//Queues the item in the BatchBackgroundWorker for parrarelization with any other items.
//NOTE: This code is not currently hit unless the user makes a dialog selection before another callback can reach the above if statement.
conversionBatchBackgroundWorker.Additem(threadIndex);
else
{
FolderBrowserDialog.ShowDialog();
conversionBatchBackgroundWorker.RunWorkerAsync(new int[] { (int)threadIndex }, FolderBrowserDialog.SelectedPath);
}
}
}
我已尝试将 DispatcherPriority 更改为 DispatcherPriority.SystemIdle - 这使得所有待处理的 ShowDialog 对话框在必须单击“确定”或“取消”之前出现,但结果仍然相同。
有没有办法阻止 ShowDialog 允许其他挂起的回调到主线程执行?
更新: 我已经修改了上面的代码,以更接近地反映在我的实际应用程序中发生的事情。所有回调操作都需要排入我创建的 BatchBackgroundWorker 类中,以便执行它们的相关转换。这就是为什么我不能在调用 ShowDialog() 之前简单地设置一个变量,说它已被调用,因为剩余的回调将尝试将自己排入当前尚未启动的 BatchBackgroundWorker (此时 BatchBackgroundWorker 线程未运行为用户对对话框的响应等待启动)。
【问题讨论】:
-
从技术上讲,阻塞当前线程是使对话框成为对话框的原因,但我可以看到您对此的推理。
-
@PCLuddite 特定的对话框实现是“阻塞的”(因为同一函数中的后续代码不会执行)但进程仍然调度(处理)事件,只是在对话框拥有的循环中,这就是 UI 仍然起作用的方式。 OP遇到的问题与此有关。
-
@user2864740 没说不是。
-
不管怎样,主要问题是:否,如果事件处理被完全抑制,则(对话框本身的)UI 将无法运行。我真的很讨厌“建议”沿着消息拦截的路线走,因为这只会使问题/问题复杂化。
标签: c# multithreading winforms callback dispatcher