当WPF应用程序关闭时自动退出线程

4

我有一个主WPF窗口,其中一个控件是我创建的用户控件。这个用户控件是一个模拟时钟,包含一个线程来更新时、分和秒针。最初它不是一个线程,而是一个定时器事件来更新小时、分钟和秒钟,但是当用户按下启动按钮时应用程序会进行一些繁重的工作,然后时钟将无法更新,所以我将其更改为了一个线程。

WPF窗口的代码片段:

     <Window
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"        
xmlns:local="clr-namespace:GParts"
       xmlns:Microsoft_Windows_Themes="clr-namespace:Microsoft.Windows.Themes
       assembly=PresentationFramework.Aero"            
       xmlns:UC="clr-namespace:GParts.UserControls"
       x:Class="GParts.WinMain"
       Title="GParts"    
       WindowState="Maximized"
       Closing="Window_Closing"    
       Icon="/Resources/Calendar-clock.png"
       x:Name="WMain"
     >
     <...>
          <!-- this is my user control -->
          <UC:AnalogClock Grid.Row="1" x:Name="AnalogClock" Background="Transparent"
           Margin="0" Height="Auto" Width="Auto"/>
     <...>
     </Window>

我的问题是当用户退出应用程序时,线程似乎仍在继续执行。我希望当主窗口关闭时,线程能够自动完成。
用户控件构造函数的代码片段:
namespace GParts.UserControls
{
/// <summary>
/// Lógica de interacción para AnalogClock.xaml
/// </summary>
public partial class AnalogClock : UserControl
{
    System.Timers.Timer timer = new System.Timers.Timer(1000);

    public AnalogClock()
    {
        InitializeComponent();

        MDCalendar mdCalendar = new MDCalendar();
        DateTime date = DateTime.Now;
        TimeZone time = TimeZone.CurrentTimeZone;
        TimeSpan difference = time.GetUtcOffset(date);
        uint currentTime = mdCalendar.Time() + (uint)difference.TotalSeconds;

        christianityCalendar.Content = mdCalendar.Date("d/e/Z", currentTime, false);

        // this was before implementing thread
        //timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
        //timer.Enabled = true;

        // The Work to perform 
        ThreadStart start = delegate()
        {

            // With this condition the thread exits when main window closes but
            // despite of this it seems like the thread continues executing after
            // exiting application because in task manager cpu is very  busy
            // 
            while ((this.IsInitialized) && 
                   (this.Dispatcher.HasShutdownFinished== false))
           {

            this.Dispatcher.Invoke(DispatcherPriority.Normal, (Action)(() =>
            {
                DateTime hora = DateTime.Now;

                secondHand.Angle = hora.Second * 6;
                minuteHand.Angle = hora.Minute * 6;
                hourHand.Angle = (hora.Hour * 30) + (hora.Minute * 0.5);

                DigitalClock.CurrentTime = hora;
            }));
        }
            Console.Write("Quit ok");
        };

        // Create the thread and kick it started!
        new Thread(start).Start();
    }

    // this was before implementing thread
    void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
    {

        this.Dispatcher.Invoke(DispatcherPriority.Normal, (Action)(() =>
        {
            DateTime hora = DateTime.Now;

            secondHand.Angle = hora.Second * 6;
            minuteHand.Angle = hora.Minute * 6;
            hourHand.Angle = (hora.Hour * 30) + (hora.Minute * 0.5);

            DigitalClock.CurrentTime = hora;
        }));
    }
 } // end class
 } // end namespace

如何在主窗口关闭并且应用程序退出时自动退出线程?
5个回答

11

只需将线程的IsBackground属性设置为true,这样它就不会阻止进程终止。

Thread t = new Thread(...) { IsBackground = true };

好的,谢谢。如果我将IsBackground属性设置为true并执行一个无限循环,例如while (true) { // do stuff },当退出应用程序时,线程会终止执行吗? - toni
1
是的。当所有非后台线程退出时,.NET会终止该进程。后台线程将被静默终止。然而,就我而言,尽管将工作线程设置为后台是正确的做法,但我仍然认为这只是部分解决方案:正如RandomEngy所指出的那样,您的工作线程基本上正在100%的CPU上等待UI线程排队工作,如果您解决了这个设计问题,那么您的终止问题也应该迎刃而解。(但将IsBackground = true设置为真仍然值得做。) - itowlson
好的,我会尝试。我想问一下您建议我更新时钟任务的最佳和高效方法。我想要一种有效的方式来更新时钟,并且当主窗口(应用程序退出)关闭时它会停止。正如您所说,一个好的方法是在后台执行线程,但是把所有的更新操作放在一个无限循环内部的线程中是否正确?还是有更好的方法来做到这一点?这个线程是否正确: while (true) { this.Dispatcher.invoke(...); Thread.Sleep(1000);} - toni

1

嗯,你面临的一个主要问题是似乎在运行无限循环,快速地排队大量的调度程序作业来更新你的时钟。一个简单的解决方法可能是在循环中放置一个Thread.Sleep(1000);语句,然后像Taylor建议的那样将你的线程设置为后台线程。

无论如何,我有点惊讶于后台工作会导致计时器无法更新。让这种方法起作用将是理想的解决方案。也许尝试一下DispatcherTimer,看看它是否可以在后台工作进行时进行更新。


调度程序只有在事件处理程序完成后才会运行。如果事件处理程序中有长时间的计算,调度程序将无法运行以分派计时器事件。 - Gabe
好的,谢谢。但是当主应用程序(窗口)关闭或用户退出应用程序时,如何退出while循环?我的while循环是否正确?我已经测试了当退出应用程序时,线程完成并到达该行:console.write("Quit OK") 但出现问题后,我发现任务管理器中的CPU使用率非常高,似乎线程确实没有完成执行。在while循环中,为了退出它和线程,什么是正确的条件,当用户退出应用程序(关闭包含用户控件模拟时钟的主窗口)时?谢谢。 - toni
我认为发生的情况是你一直往工作队列中倾泻调度程序任务,导致它们在应用程序关闭之前全部执行完毕。在循环中加入一个1秒的休眠语句,然后将线程更改为后台线程应该可以解决这个问题。 - RandomEngy
gabe:他使用的调度程序作业并不长时间运行。 后台线程将以全速输出它们,UI 线程将几乎全天候处理它们。 - RandomEngy

0
最后,我混合了两个解决方案,RandomEngy 和 Taylor,但效果不理想(应用程序退出失败),所以我决定将它们与另一个线程中的解决方案相结合:

WPF 取消背景工作器应用程序退出

在我的主窗口应用程序中,从 XAML 中设置了 ShutdownMode 属性为 OnMainWindowClose,正如 Thomas 在他的评论中所说。
谢谢!

0

我知道你已经解决了你的问题,但是由于我遇到了类似的问题,涉及到运行线程和正确退出应用程序,所以我想分享一下我是如何解决这个问题的。

最终,我绕过了你使用的调度程序/线程模型,而是选择使用BackgroundWorker类(System.ComponentModel.BackgroundWorker)。 这是一个简单的四步过程:

  1. 创建一个私有成员来保存BackgroundWorker
  2. 在构造函数中分配BackgroundWorker.DoWork事件处理程序
  3. 定义DoWork事件处理程序方法
  4. 在我想要启动实际工作时调用_myBackgroundWorker.RunWorkAsync()

关于在WPF中使用Dispatcher的文章中嵌入了使用BackgroundWorker的详细信息。

希望对某些人有所帮助...


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接