我有一个 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);
}
* 编辑 * 我也应该添加 Splashscreen 代码:
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() 调用之前和现在之后。初始屏幕显示更新其显示,而主应用程序验证某些安全性。然后它变成一个登录屏幕,然后在主应用程序从服务器加载数据时显示更多更新。一旦主窗体(通过事件)表明它已经完成了它的工作,SplashScreen 就会关闭。