我们的程序运行正常,直到某人锁定计算机或屏幕保护程序弹出(但不是ctrl + alt + delete)。一旦计算机解锁/屏幕保护程序关闭,应用程序将停止绘制除标题栏以外的所有内容,并停止响应输入 - 它显示一个无法移动或关闭的大部分白色窗口。
(应用程序冻结的示例 - 山脉来自我的桌面背景)
如果我们让它静置大约5到10分钟,它会恢复生命,并且不会再次挂起(即使在锁定计算机/屏幕保护程序弹出窗口后),直到应用程序重新启动
调试很困难,因为只有在手动打开.exe的情况下,从Visual Studio启动程序时才会发生这种情况。
只有在显示启动画面时才会发生 - 如果我删除代码以显示启动画面,它就会停止发生。但是,我们需要启动画面。
我在this page 上尝试了所有建议;唯一没有发生的事情是使用Microsoft.VisualBasic.WindowsFormsApplicationBase
,但这导致所有种类的其他问题。
互联网上关于此的信息似乎很少 - 以前是否有人遇到过类似的问题?
以下是相关代码:
//Multiple programs use this login form, all have the same issue
public partial class LoginForm<TMainForm>
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;
}
答案 0 :(得分:3)
DoEvents是非常不受欢迎的,并不一定能达到你的想象。 DoEvents告诉CLR参与Windows消息循环(对于启动屏幕),但不一定为其他线程提供任何处理时间。 Thread.Sleep()
将为其他线程提供处理的机会,但不一定允许启动屏幕的Windows消息循环继续传送消息。所以如果你必须使用一个循环你真的需要两个,但是在一分钟之内我会建议完全远离这个循环。除了那个循环问题,我没有看到任何明确的方式正在清理启动线程。您需要在某处发生某种Thread.Join()
或Thread.Abort()
。
我不想使用Application.DoEvents()
循环,而是喜欢使用ManualResetEvent来同步启动表单和调用线程。这样,ShowSplash()方法在显示splash之前不会返回。在那之后的任何时候我们显然可以关闭它,因为我们知道它已经完成了显示。
这是一个包含一些好例子的帖子:.NET Multi-threaded Splash Screens in C#
以下是我如何修改我最喜欢的示例@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();
}
/// <summary>
/// Show the Splash Screen (Loading...)
/// </summary>
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);
}
/// <summary>
/// Close the splash (Loading...) screen
/// </summary>
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()
方法,只需将表单属性调整为对话框,如果这是所需的结果(例如:BorderStyle,MaximizeBox,MinimizeBox,ControlBox,TopMost)。
重要编辑:好我是人类,我搞砸了,忘记了ShowDialog是一种阻止方法。虽然这确实否定了所有者处理问题,但我仍然建议不要将ShowDialog用于您的主要申请表格,除非您能为其提供非外观或线程相关的重要理由(因为这些应该通过其他技术修复)。尽管我有失误,但建议仍然合理。
您没有指定您正在使用哪些控件,或者您是否在应用程序中进行了任何自定义绘制。但是你需要记住,锁定计算机时会强行关闭一些窗口句柄。例如,如果您有一些自定义绘制的控件并且正在缓存字体,画笔或其他GDI资源,则需要在代码中使用一些try { ... } catch { ... }
块来处理,然后在绘制期间引发异常时重建缓存的GDI资源。在我自定义绘制列表框并缓存一些GDI对象之前,我遇到了这个问题。如果你的应用程序中有任何自定义绘图代码,包括在启动画面中,请仔细检查所有GDI对象是否已妥善处置/清理。
答案 1 :(得分:1)
在上面的代码片段中添加几行代码后,我可以编译工作程序。但是,我无法重现问题(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<TMainForm> : 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>(MainForm.ShowSplashScreen);
loginForm.Visible = true;
Application.Run(loginForm);
}
}
答案 2 :(得分:1)
几年后(代码不再在我面前),我会为遇到此问题的其他人添加答案。
问题与Hans Passant had guessed完全相同。问题在于,由于.Net框架中存在一些令人难以置信的模糊和无害的错误,InvokeRequired
有时会在返回false
时返回true
,从而导致应在GUI上运行的代码线程在后台运行(由于一些更加模糊和无害的错误,导致我看到的行为)。
解决方案是不依赖于InvokeRequired
,使用与此类似的黑客攻击:
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
这个解决方案以及对问题原因的深入研究被发现here。
答案 3 :(得分:0)
因为没有工作实例
你可以尝试删除 Application.DoEvents(); 并插入thread.sleep吗?
Application.DoEvents(); 让我们说非常邪恶。
答案 4 :(得分:0)
通过我对您的代码进行的快速扫描,看起来您的问题的关键可能是使用
Application.Run(_splashForm);
理想情况下,您可以在线程中使用它,但也许它也可以与您的DoEvent一起使用。对不起,如果你这样做,我就错过了......
答案 5 :(得分:0)
在我们的应用程序中,我们在启动屏幕上遇到了类似的问题。我们希望有一个带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;
}
答案 6 :(得分:0)
删除此行,您不需要它,当默认为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();
}
答案 7 :(得分:0)
您是否尝试过使用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();
}
答案 8 :(得分:0)
这是黑暗中的一个镜头:当我们闲置时,我们也要求线程进入睡眠状态。我不确定这会有所帮助,但值得一试:
while(_showSplash) {
System.Threading.Thread.Sleep(500);
Application.DoEvents();
}
答案 9 :(得分:0)
我认为您的问题是因为您使用的是Form.ShowDialog
,而不是Application.Run
。 ShowDialog运行一个在主消息循环之上运行的受限消息循环,并忽略一些Windows消息。
这样的事情应该有效:
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[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();
}
}