离开屏幕保护程序或锁定计算机后程序挂起

我们的程序工作正常,直到有人锁定计算机或屏幕保护程序弹出(但不是ctrl + alt + delete) 。 一旦计算机解锁/屏幕保护程序关闭,应用程序将停止绘制除标题栏以外的所有内容,并停止响应输入 – 它显示一个无法移动或关闭的大部分白色窗口。

应用程序冻结的示例

(应用程序冻结示例 – 山脉来自我的桌面背景)

如果我们让它静置大约5到10分钟,它会恢复生命,并且不会再次挂起(即使在锁定计算机/屏幕保护程序弹出窗口之后),直到应用程序重新启动。

调试很困难,因为只有在手动打开.exe时才从Visual Studio启动程序时才会发生这种情况。

它只在显示启动画面时发生 – 如果我删除代码以显示启动画面,它就会停止发生。 但是,我们需要启动画面。

我在这个页面上尝试了所有的建议 ; 唯一没有发生的是使用Microsoft.VisualBasic.WindowsFormsApplicationBase ,但这会导致各种其他问题。

互联网上有关这方面的信息似乎很少 – 以前有人遇到过类似的问题吗?


这是相关的代码:

 //Multiple programs use this login form, all have the same issue public partial class LoginForm where TMainForm : Form, new() { private readonly Action _showLoadingForm; public LoginForm(Action showLoadingForm) { ... _showLoadingForm = showLoadingForm; } private void btnLogin_Click(object sender, EventArgs e) { ... this.Hide(); ShowLoadingForm(); //Problem goes away when commenting-out this line new TMainForm().ShowDialog(); this.Close(); } private void ShowLoadingForm() { Thread loadingFormThread = new Thread(o => _showLoadingForm()); loadingFormThread.IsBackground = true; loadingFormThread.SetApartmentState(ApartmentState.STA); loadingFormThread.Start(); } } 

以下是其中一个程序中使用的_showLoadingForm操作之一的示例:

 public static bool _showSplash = true; public static void ShowSplashScreen() { //Ick, DoEvents! But we were having problems with CloseSplashScreen being called //before ShowSplashScreen - this hack was found at //https://stackoverflow.com/questions/48916/multi-threaded-splash-screen-in-c/48946#48946 using(SplashForm splashForm = new SplashForm()) { splashForm.Show(); while(_showSplash) Application.DoEvents(); splashForm.Close(); } } //Called in MainForm_Load() public static void CloseSplashScreen() { _showSplash = false; } 

启动画面问题

DoEvents是非常不受欢迎的,并不一定能达到您的想象。 DoEvents告诉CLR参与Windows消息循环(对于启动屏幕),但不一定为其他线程提供任何处理时间。 Thread.Sleep()将为其他线程提供处理的机会,但不一定允许启动屏幕的Windows消息循环继续泵送消息。 所以如果你必须使用一个循环你真的需要两个,但是在一分钟之内我会建议完全远离这个循环。 除了循环问题,我没有看到任何明确的方式正在清理启动线程。 你需要在某处发生某种Thread.Join()Thread.Abort()

而不是使用Application.DoEvents()循环,我喜欢使用ManualResetEvent来同步启动窗体启动与调用线程。 这样,ShowSplash()方法在显示splash之前不会返回。 在那之后的任何时候我们显然可以关闭它,因为我们知道它已经完成显示。

这是一个包含一些好例子的线程: C#中的.NETmultithreading启动画面

以下是我修改我最喜欢的示例@AdamNosfinger发布的方法,包括一个ManualResetEvent来同步ShowSplash方法和初始屏幕线程:

 public partial class FormSplash : Form { private static Thread _splashThread; private static FormSplash _splashForm; // This is used to make sure you can't call SplashScreenClose before the SplashScreenOpen has finished showing the splash initially. static ManualResetEvent SplashScreenLoaded; public FormSplash() { InitializeComponent(); // Signal out ManualResetEvent so we know the Splash form is good to go. SplashScreenLoaded.Set(); } ///  /// Show the Splash Screen (Loading...) ///  public static void ShowSplash() { if (_splashThread == null) { // Setup our manual reset event to syncronize the splash screen thread and our main application thread. SplashScreenLoaded = new ManualResetEvent(false); // show the form in a new thread _splashThread = new Thread(new ThreadStart(DoShowSplash)); _splashThread.IsBackground = true; _splashThread.Start(); // Wait for the splash screen thread to let us know its ok for the app to keep going. // This next line will not return until the SplashScreen is loaded. SplashScreenLoaded.WaitOne(); SplashScreenLoaded.Close(); SplashScreenLoaded = null; } } // called by the thread private static void DoShowSplash() { if (_splashForm == null) _splashForm = new FormSplash(); // create a new message pump on this thread (started from ShowSplash) Application.Run(_splashForm); } ///  /// Close the splash (Loading...) screen ///  public static void CloseSplash() { // need to call on the thread that launched this splash if (_splashForm.InvokeRequired) _splashForm.Invoke(new MethodInvoker(CloseSplash)); else Application.ExitThread(); } } 

主要表格问题

看起来好像是使用ShowDialog从登录窗口启动主窗体,然后关闭登录表单。 我理解正确吗? 如果是这样的话就不好了。 ShowDialog适用于应用程序的子窗口,并且希望拥有所有者窗口,如果未在方法参数中指定所有者窗体,则假定当前活动窗口为所有者。 请参阅MSDN

因此,您的主要表单是假设登录表单是其父表单,但在显示主表单后不久您将关闭登录表单。 所以我不确定应用程序在那时处于什么状态。 您应该考虑使用标准的Form.Show()方法,只需将Form属性调整为对话框,如果这是所需的结果(例如:BorderStyle,MaximizeBox,MinimizeBox,ControlBox,TopMost)。

重要编辑:好的我是人类,我搞砸了,忘记了ShowDialog是一种阻止方法。 虽然这确实否定了所有者处理问题,但我仍然建议不要将ShowDialog用于您的主申请表,除非您能为其提供一个非外观或线程相关的重要理由(因为这些应该通过其他技术修复)。 尽管我有失误,但建议仍然合理。

可能的绘画问题

您没有指定您正在使用哪些控件,或者您是否在应用程序中进行了任何自定义绘制。 但是您需要记住,当您锁定计算机时,某些窗口句柄将被强行关闭。 例如,如果您有一些自定义绘制控件并缓存字体,画笔或其他GDI资源,则需要在代码中使用一些try { ... } catch { ... }块来处理然后重建缓存的GDI资源在绘画期间引发exception时 在我自定义绘制列表框并缓存一些GDI对象之前,我遇到了这个问题。 如果你的应用程序中有任何自定义绘画代码,包括在初始屏幕中,请仔细检查所有GDI对象是否处理得很好/清理干净。

在上面的代码片段中添加几行代码后,我可以编译一个工作程序 。 但是, 我无法重现该问题 (Windows 7 Starter)。 我试着锁定电脑,并启动屏幕保护程序。 我在启动画面处于活动状态时执行了此操作,而在其他情况下,主窗口始终保持响应。 我认为这里肯定会发生其他事情,可能是在主窗口初始化期间。

这是代码,也许它可以帮助其他人找出问题所在。

 using System; using System.Threading; using System.Windows.Forms; public class MainForm : Form { //Here is an example of one of the _showLoadingForm actions used in one of the programs: public static bool _showSplash = true; public static void ShowSplashScreen() { //Ick, DoEvents! But we were having problems with CloseSplashScreen being called //before ShowSplashScreen - this hack was found at //http://stackoverflow.com/questions/48916/multi-threaded-splash-screen-in-c/48946#48946 using(SplashForm splashForm = new SplashForm()) { splashForm.Show(); while(_showSplash) Application.DoEvents(); splashForm.Close(); } } //Called in MainForm_Load() public static void CloseSplashScreen() { _showSplash = false; } public MainForm() { Text = "MainForm"; Load += delegate(object sender, EventArgs e) { Thread.Sleep(3000); CloseSplashScreen(); }; } } //Multiple programs use this login form, all have the same issue public class LoginForm : Form where TMainForm : Form, new() { private readonly Action _showLoadingForm; public LoginForm(Action showLoadingForm) { Text = "LoginForm"; Button btnLogin = new Button(); btnLogin.Text = "Login"; btnLogin.Click += btnLogin_Click; Controls.Add(btnLogin); //... _showLoadingForm = showLoadingForm; } private void btnLogin_Click(object sender, EventArgs e) { //... this.Hide(); ShowLoadingForm(); //Problem goes away when commenting-out this line new TMainForm().ShowDialog(); this.Close(); } private void ShowLoadingForm() { Thread loadingFormThread = new Thread(o => _showLoadingForm()); loadingFormThread.IsBackground = true; loadingFormThread.SetApartmentState(ApartmentState.STA); loadingFormThread.Start(); } } public class SplashForm : Form { public SplashForm() { Text = "SplashForm"; } } public class Program { public static void Main() { var loginForm = new LoginForm(MainForm.ShowSplashScreen); loginForm.Visible = true; Application.Run(loginForm); } } 

几年后(代码不再在我面前) ,我会为遇到此问题的其他人添加答案。


这个问题与Hans Passant猜测的完全一样。 问题是,由于.Net框架中存在一些令人难以置信的模糊和无害的错误, InvokeRequired有时会返回false当它应该返回true ,导致应该在GUI线程上运行的代码在后台运行(由于某些更隐蔽和无害的错误,导致我看到的行为)

解决方案是不依赖于InvokeRequired ,使用类似于此的hack:

 void Main() { Thread.Current.Name = "GuiThread"; ... } bool IsGuiThread() { return Thread.Current.Name == "GuiThread"; } //Later, call IsGuiThread() to determine if GUI code is being run on GUI thread 

这里找到了这个解决方案,以及对问题原因的深入研究。

因为没有工作的例子

你可以尝试删除Application.DoEvents(); 并插入thread.sleep?

Application.DoEvents(); 让我们说非常邪恶。

从我对您的代码进行的快速扫描,看起来您的问题的关键可能正在使用

 Application.Run(_splashForm); 

理想情况下,你会在一个线程中使用它,但也许它也可以与你的DoEvents一起使用。 对不起,如果你这样做,我只是错过了…

在我们的应用程序中,我们在启动画面上遇到了类似的问题 我们希望有一个带GIF动画的闪屏(不要责怪我,这是管理层的决定)。 只有当splashScreen有自己的消息循环时才能正常工作。 因为我认为DoEvents是你问题的关键,我告诉你,我们是如何解决它的。 希望它能帮助您解决问题!

我们将以这种方式显示启动画面:

 // AnimatedClockSplashScreen is a special form from us, it can be any other! // Our form is set to be TopMost splashScreen = new AnimatedClockSplashScreen(); Task.Factory.StartNew(() => Application.Run(splashScreen)); 

启动画面很简单,包含时钟的GIF动画。 它没有任何循环,所以它不会随时变钢。

当需要关闭启动时,我们以这种方式执行:

 if (splashScreen != null) { if (splashScreen.IsHandleCreated) { try { splashScreen.Invoke(new MethodInvoker(() => splashScreen.Close())); } catch (InvalidOperationException) { } } splashScreen.Dispose(); splashScreen = null; } 

删除此行,您不需要它,当默认为mta时,您将它强制为单个线程。 采取默认。

 loadingFormThread.SetApartmentState(ApartmentState.STA); 

改变以下内容:

 using(SplashForm splashForm = new SplashForm()) { splashForm.Show(); while(_showSplash) Application.DoEvents(); splashForm.Close(); } 

至:

 SplashForm splashForm = new SplashForm()) splashForm.Show(); 

改变这个:

 public static void CloseSplashScreen() { _showSplash = false; } 

对此:

 public static void CloseSplashScreen() { splashForm.Close(); } 

这是黑暗中的一个镜头:当我们闲置时,我们也要求线程进入睡眠状态。 我不确定这会有所帮助,但值得一试:

  while(_showSplash) { System.Threading.Thread.Sleep(500); Application.DoEvents(); } 

您是否尝试使用WaitHandle在线程中显示表单?

就像是:

 EventWaitHandle _waitHandle = new AutoResetEvent(false); public static void ShowSplashScreen() { using(SplashForm splashForm = new SplashForm()) { splashForm.Show(); _waitHandle.WaitOne(); splashForm.Close(); } } //Called in MainForm_Load() public static void CloseSplashScreen() { _waitHandle.Set(); } 

我认为你的问题是因为你使用的是Form.ShowDialog ,而不是Application.Run 。 ShowDialog运行一个在主消息循环之上运行的受限消息循环,并忽略一些Windows消息。

像这样的东西应该工作:

 static class Program { ///  /// The main entry point for the application. ///  [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault( false ); Application.Run( new MainForm() ); } } public partial class MainForm: Form { FormSplash dlg = null; void ShowSplashScreen() { var t = new Thread( () => { using ( dlg = new FormSplash() ) dlg.ShowDialog(); } ); t.SetApartmentState( ApartmentState.STA ); t.IsBackground = true; t.Start(); } void CloseSplashScreen() { dlg.Invoke( ( MethodInvoker ) ( () => dlg.Close() ) ); } public MainForm() { ShowSplashScreen(); InitializeComponent(); Thread.Sleep( 3000 ); // simulate big form CloseSplashScreen(); } }