当屏幕保护程序设置在Windows 7中更改时应用程序冻结 - System.Threading.Timer 罪魁祸首

本文关键字:冻结 应用程序 System 罪魁祸首 Timer Threading 设置 程序 屏幕保护 Windows | 更新日期: 2023-09-27 18:31:22

我有一个C#/TCP/Winform应用程序已经运行了很长时间,但我的一个用户发现了一个我可以轻松复制的问题,但我似乎无法解决。

如果应用在 Windows 7 上打开,并且用户随后更改其屏幕保护程序设置中的任何选项(无论是正在使用的屏幕保护程序还是时间),则应用将冻结,UI 将变得无响应。单击表单上的任意位置只会使系统"叮当"。

当应用程序在调试模式下通过 Visual Studio 运行时,问题将不复存在,但一旦超出 VS 的范围,就会回到冻结状态。

经过一些修补和测试,我似乎已经将我的问题缩小到每秒运行一次的System.Threading.Timer。我一直在稳步减少计时器的功能,并发现即使计时器的事件什么都不做,它仍然会锁定应用程序。如果我在更改屏幕保护程序设置之前禁用代码中的计时器或取消应用程序中的计时器,则应用程序将在屏幕保护程序更改后恢复功能(尽管它似乎仍然冻结了大约 2-3 秒)。

这里的这段代码似乎是使应用程序可冻结(在 WinForm 代码中)所需的全部内容:

    /// <summary>
    /// Starts the countdown timer for unit alerts
    /// </summary>
    private void startCountDownTimer()
    {
        Object timeState = new object();
        this._timerCall = new System.Threading.TimerCallback(this.countDown);
        this._countdownTimer = new System.Threading.Timer(_timerCall, timeState, 0, 1000);
    }
    /// <summary>
    /// Invokes the countdown logic for unit alerts
    /// </summary>
    /// <param name="state"></param>
    private void countDown(Object state)
    {
        // REMOVED AND STILL FREEZING
    }

请注意,即使注释掉了 countDown 的内容,也会发生此冻结,因此计时器除了每秒触发一次外什么都不做。

如果我启动进程,然后将远程调试器附加到它,则输出中没有任何内容表明应用程序有任何问题。该表单实际上仍然会触发诸如"已激活"之类的事件:

*** MAIN WINDOW ACTIVATED ***
*** MAIN WINDOW ACTIVATED ***

但是,似乎没有其他任何东西被触发,应用程序必须通过EndTask被杀死或关闭。如果在调试器仍附加时使用 EndTask,我突然收到错误:

A first chance exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll
===================================
ERR: Invoke or BeginInvoke cannot be called on a control until the window handle has been created.
       at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
   at System.Windows.Forms.Control.BeginInvoke(Delegate method, Object[] args)
   at System.Windows.Forms.Control.BeginInvoke(Delegate method)
   at Wcsg.UI.Windows.CadClient.client_MessageReceived(TcpMessageReceivedEventArgs mrea) in C:'Users'---------'Documents'Visual Studio 2010'Projects'Dispatch-DEVELOPMENT'CadClient'Forms'CadClientForm.cs
   at Wcsg.Net.Tcp.WcsgTcpClient.processStream(Int32 count)
   at Wcsg.Net.Tcp.WcsgTcpClient.performSocketRead(IAsyncResult ar)
---------------------

我最终得到的错误似乎与表单的关闭有关,而表单最终可以处理套接字上的消息。

我正在寻找任何方向来寻找这里。

*编辑*

当预期的解决方法(见答案)不起作用时,有人问我程序中的 Main 方法。 这是主代码:

[STAThread]
    static void Main(String[] args)
    {
        // check for other running CadClients
        bool createdNew = _mutex.WaitOne(TimeSpan.Zero, false);
        if (createdNew) // first-run, launch Status Monitor on load
        {
            List<String> newArgs = new List<string>(args);
            newArgs.Add("-SM");
            args = newArgs.ToArray();
        }
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        AppDomain currentDomain = AppDomain.CurrentDomain;
        Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
        // try to catch issue 13445 -- see testing notes
        CadClient cc = new CadClient(args);
        CadExceptionHandler ceh = new CadExceptionHandler(ref cc);
        currentDomain.UnhandledException += new UnhandledExceptionEventHandler(ceh.CurrentDomain_UnhandledException);
        Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(ceh.Application_ThreadException);
        Application.Run(cc);
    }

*编辑*我可能还应该添加启动画面代码:

    private void showSplashScreen()
    {
        WaitCallback wcb = new WaitCallback(doSplashScreen);
        ThreadPool.QueueUserWorkItem(wcb);
    }
    private void doSplashScreen(object state)
    {
        if (this._splash == null)
            this._splash = new SplashForm(this);
        this._splash.FormClosed += new FormClosedEventHandler(_splash_FormClosed);
        this._splash.Show();
        while (this._splash != null && !this._splash.WorkDone)
            Application.DoEvents();
        this._splash.Close();
    }

方法showSplashScreen()在主窗体的构造函数中调用 - 最初在InitializeComponents()调用之前和现在之后。初始屏幕显示更新其显示,而主应用验证某些安全性。然后它变成一个登录屏幕,然后在主应用程序从服务器加载数据时显示更多 udpate。一旦主窗体指示(通过事件)它已完成其工作,启动画面就会关闭。

当屏幕保护程序设置在Windows 7中更改时应用程序冻结 - System.Threading.Timer 罪魁祸首

使用了 @HansPassant 的注释,发现我们的启动/登录屏幕的 showSplash 方法没有在 Application.Run 之前被调用,而是在我们主窗体的 InitializeComponent 方法之前被调用。将 this.showSplash 移到 InitializeComponent() 下方的行并重新编译,问题似乎消失了。