簡體   English   中英

為什么Window.ShowDialog在TaskScheduler任務中沒有阻塞?

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

我正在使用自定義TaskScheduler來串行執行任務隊列。 任務應該顯示一個窗口,然后阻止,直到窗口自行關閉。 不幸的是,調用Window.ShowDialog()似乎沒有阻塞,因此任務完成,窗口永遠不會顯示。

如果我在調用ShowDialog之后放置一個斷點,我可以看到表單已經打開但是在正常執行下,Task似乎很快就會結束你無法看到它。

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

沒有什么明顯錯的。 除了缺少的內容之外 ,您沒有做任何事情來確保報告在任務中引發的異常。 你編寫它的方式,永遠不會報告這樣的異常,你只會看到代碼無法運行。 就像一個剛剛消失的對話框。 你需要寫這樣的東西:

    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線程。 如果這是你的對話框那么你就必須努力后診斷線程問題。

不要這樣做。

顯然,你正在使用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()退出之前,其他計划任務將不會運行。

我有一個類似的問題,在啟動時我無法通過ShowDialog阻止執行。 最后,我發現類屬性內部是命令行參數,用於自動使用適當的憑據登錄。 這節省了開發時間,每次編譯時都不輸入用戶名和密碼。 一旦我刪除了Dialog的行為符合預期,那么搜索可能會干擾正常執行路徑的進程是值得的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM