我正在使用自定义TaskScheduler
来执行串行任务队列。任务应该显示一个窗口,然后阻止,直到窗口自行关闭。不幸的是,调用Window.ShowDialog()
似乎没有阻止,因此任务完成,窗口永远不会显示。
如果我在调用ShowDialog
之后设置断点,我可以看到表单已经打开但是在正常执行下,任务似乎很快就会结束,你无法看到它。
我的TaskScheduler实现取自上一个问题:
public sealed class StaTaskScheduler : TaskScheduler, IDisposable
{
private readonly List<Thread> threads;
private BlockingCollection<Task> tasks;
public override int MaximumConcurrencyLevel
{
get { return threads.Count; }
}
public StaTaskScheduler(int concurrencyLevel)
{
if (concurrencyLevel < 1) throw new ArgumentOutOfRangeException("concurrencyLevel");
this.tasks = new BlockingCollection<Task>();
this.threads = Enumerable.Range(0, concurrencyLevel).Select(i =>
{
var thread = new Thread(() =>
{
foreach (var t in this.tasks.GetConsumingEnumerable())
{
this.TryExecuteTask(t);
}
});
thread.IsBackground = true;
thread.SetApartmentState(ApartmentState.STA);
return thread;
}).ToList();
this.threads.ForEach(t => t.Start());
}
protected override void QueueTask(Task task)
{
tasks.Add(task);
}
protected override IEnumerable<Task> GetScheduledTasks()
{
return tasks.ToArray();
}
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
return Thread.CurrentThread.GetApartmentState() == ApartmentState.STA && TryExecuteTask(task);
}
public void Dispose()
{
if (tasks != null)
{
tasks.CompleteAdding();
foreach (var thread in threads) thread.Join();
tasks.Dispose();
tasks = null;
}
}
}
我的申请代码:
private StaTaskScheduler taskScheduler;
...
this.taskScheduler = new StaTaskScheduler(1);
Task.Factory.StartNew(() =>
{
WarningWindow window = new WarningWindow(
ProcessControl.Properties.Settings.Default.WarningHeader,
ProcessControl.Properties.Settings.Default.WarningMessage,
processName,
ProcessControl.Properties.Settings.Default.WarningFooter,
ProcessControl.Properties.Settings.Default.WarningTimeout * 1000);
window.ShowDialog();
}, CancellationToken.None, TaskCreationOptions.None, this.taskScheduler);
答案 0 :(得分:4)
没有明显的错误。除了缺少之外,您没有做任何事情来确保报告在任务中引发的异常。你编写它的方式,永远不会报告这样的异常,你只会看到代码无法运行。就像一个刚刚消失的对话框。你需要写这样的东西:
Task.Factory.StartNew(() => {
// Your code here
//...
}, CancellationToken.None, TaskCreationOptions.None, taskScheduler)
.ContinueWith((t) => {
MessageBox.Show(t.Exception.ToString());
}, TaskContinuationOptions.OnlyOnFaulted);
有很好的赔率,你现在会看到报告的InvalidOperationException。使用Debug + Exceptions进一步诊断它,勾选CLR异常的Thrown复选框。
请注意,此任务计划程序不会神奇地使您的代码线程安全或适合运行另一个UI线程。它不是为此而设计的,它只应用于保持单线程COM组件的快乐。您必须遵守在另一个线程上运行UI的有时严苛的后果。换句话说,请勿触摸主线程上的UI属性。并且对话框根本不像对话框,因为它没有所有者。因此,它随机消失在另一个窗口后面,或者被用户意外关闭,因为他点击了一下,从未计入过无窗口出现的窗口。
最后但并非最不重要的是SystemEvents类造成的持久痛苦。哪个需要猜测哪个线程是UI线程,它将选择第一个STA线程。如果这是您的对话框,那么您将很难非常以后很难诊断线程问题。
不要这样做。
答案 1 :(得分:2)
显然,你正在使用Stephen Toub的StaTaskScheduler
。 不旨在运行涉及UI的任务。实质上,您正在尝试在后台线程上显示带有window.ShowDialog()
的模态窗口,该窗口与主UI线程无关。我怀疑window.ShowDialog()
会立即完成错误,任务也是如此。等待任务并观察错误:
try
{
await Task.Factory.StartNew(() =>
{
WarningWindow window = new WarningWindow(
ProcessControl.Properties.Settings.Default.WarningHeader,
ProcessControl.Properties.Settings.Default.WarningMessage,
processName,
ProcessControl.Properties.Settings.Default.WarningFooter,
ProcessControl.Properties.Settings.Default.WarningTimeout * 1000);
window.ShowDialog();
}, CancellationToken.None, TaskCreationOptions.None, this.taskScheduler);
}
catch(Exception ex)
{
MessageBox.Show(ex.Message)
}
如果确实想要显示来自后台STA线程的窗口,则需要运行Dispatcher循环:
var task = Task.Factory.StartNew(() =>
{
System.Windows.Threading.Dispatcher.InvokeAsync(() =>
{
WarningWindow window = new WarningWindow(
ProcessControl.Properties.Settings.Default.WarningHeader,
ProcessControl.Properties.Settings.Default.WarningMessage,
processName,
ProcessControl.Properties.Settings.Default.WarningFooter,
ProcessControl.Properties.Settings.Default.WarningTimeout * 1000);
window.Closed += (s, e) =>
window.Dispatcher.InvokeShutdown();
window.Show();
});
System.Windows.Threading.Dispatcher.Run();
}, CancellationToken.None, TaskCreationOptions.None, this.taskScheduler);
但请注意,此窗口不对于任何主UI线程窗口都是模态的。此外,您将阻止StaTaskScheduler
循环,因此在窗口关闭并退出Dispatcher.Run()
之前,其他计划任务将无法运行。
答案 2 :(得分:0)
我遇到了类似的问题,在启动时我无法通过ShowDialog阻止执行。最后,我发现类属性内部是命令行参数,用于自动使用适当的凭据登录。这节省了开发时间,每次编译时都不输入用户名和密码。一旦我删除了Dialog的行为符合预期,那么搜索可能会干扰正常执行路径的进程是值得的。