简体   繁体   English

为什么Window.ShowDialog在TaskScheduler任务中没有阻塞?

[英]Why is Window.ShowDialog not blocking in TaskScheduler Task?

I'm using a custom TaskScheduler to execute a task queue in serial. 我正在使用自定义TaskScheduler来串行执行任务队列。 The task is supposed to display a window and then block until the window closes itself. 任务应该显示一个窗口,然后阻止,直到窗口自行关闭。 Unfortunately calling Window.ShowDialog() doesn't seem to block so the task completes and the window never displays. 不幸的是,调用Window.ShowDialog()似乎没有阻塞,因此任务完成,窗口永远不会显示。

If I put a breakpoint after the call to ShowDialog I can see the form has opened but under normal execution the Task seems to end so quickly you cant see it. 如果我在调用ShowDialog之后放置一个断点,我可以看到表单已经打开但是在正常执行下,Task似乎很快就会结束你无法看到它。

My TaskScheduler implementation taken from a previous question: 我的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;
        }
    }
}

My Application Code: 我的申请代码:

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);

Nothing obviously wrong. 没有什么明显错的。 Except what is missing , you are not doing anything to ensure that an exception that's raised in the task is reported. 除了缺少的内容之外 ,您没有做任何事情来确保报告在任务中引发的异常。 The way you wrote it, such an exception will never be reported and you'll just see code failing to run. 你编写它的方式,永远不会报告这样的异常,你只会看到代码无法运行。 Like a dialog that just disappears. 就像一个刚刚消失的对话框。 You'll need to write something like this: 你需要写这样的东西:

    Task.Factory.StartNew(() => {
        // Your code here
        //...
    }, CancellationToken.None, TaskCreationOptions.None, taskScheduler)
    .ContinueWith((t) => {
        MessageBox.Show(t.Exception.ToString());
    }, TaskContinuationOptions.OnlyOnFaulted);

With good odds that you'll now see an InvalidOperationException reported. 有很好的赔率你现在会看到报告的InvalidOperationException。 Further diagnose it with Debug + Exceptions, tick the Thrown checkbox for CLR exceptions. 使用Debug + Exceptions进一步诊断它,勾选CLR异常的Thrown复选框。

Do beware that this task scheduler doesn't magically makes your code thread-safe or fit to run another UI thread. 请注意,此任务调度程序不会神奇地使您的代码线程安全或适合运行另一个UI线程。 It wasn't made for that, it should only be used to keep single-threaded COM components happy. 它不是为此而设计的,它只应用于保持单线程COM组件的快乐。 You must honor the sometimes draconian consequences of running UI on another thread. 您必须遵守在另一个线程上运行UI的有时严苛的后果。 In other words, don't touch properties of UI on the main thread. 换句话说,请勿触摸主线程上的UI属性。 And the dialog not acting like a dialog at all since it doesn't have an owner. 并且对话框根本不像对话框,因为它没有所有者。 And it thus randomly disappearing behind another window or accidentally getting closed by the user because he was clicking away and never counted on a window appearing from no-where. 因此,它随机消失在另一个窗口后面,或者被用户意外关闭,因为他点击了,并且从未计入过无窗口出现的窗口。

And last but not least the long-lasting misery caused by the SystemEvents class. 最后但并非最不重要的是SystemEvents类造成的长期痛苦。 Which needs to guess which thread is the UI thread, it will pick the first STA thread. 哪个需要猜测哪个线程是UI线程,它将选择第一个STA线程。 If that's your dialog then you'll have very hard to diagnose threading problems later. 如果这是你的对话框那么你就必须努力后诊断线程问题。

Don't do it. 不要这样做。

Apparently, you're using Stephen Toub's StaTaskScheduler . 显然,你正在使用Stephen Toub的StaTaskScheduler It is not intended to run tasks involving the UI. 用于运行涉及UI的任务。 Essentially, you're trying to display a modal window with window.ShowDialog() on a background thread which has nothing to do with the main UI thread. 本质上,您正在尝试在后台线程上显示一个带有window.ShowDialog()的模态窗口,该窗口与主UI线程无关。 I suspect the window.ShowDialog() instantly finishes with an error, and so does the task. 我怀疑window.ShowDialog()立即完成一个错误,任务也是如此。 Await the task and observe the errors: 等待任务并观察错误:

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)
}

If you really want to show a window from a background STA thread, you need to run a Dispatcher loop: 如果您确实想要从后台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);

Note however, this window will not be modal as to any main UI thread window. 但请注意,此窗口不是任何主UI线程窗口的模态。 Moreover, you'd block the StaTaskScheduler loop, so its other scheduled tasks won't run until the window is closed and Dispatcher.Run() exits. 此外,您将阻止StaTaskScheduler循环,因此在窗口关闭且Dispatcher.Run()退出之前,其他计划任务将不会运行。

I had a similar problem where on start up I could not manage to block execution via a ShowDialog. 我有一个类似的问题,在启动时我无法通过ShowDialog阻止执行。 Eventually I discovered that inside the class properties were Command Line Arguments that was used to automatically log in with appropriate credentials. 最后,我发现类属性内部是命令行参数,用于自动使用适当的凭据登录。 This saved time during development not to enter a username and password every time you compile. 这节省了开发时间,每次编译时都不输入用户名和密码。 As soon as I removed that the Dialog behaved as expected so it's worth while to search for processes that might interfere with the normal execution path. 一旦我删除了Dialog的行为符合预期,那么搜索可能会干扰正常执行路径的进程是值得的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM