I want to show a little loading form while some time consuming operations are beeing performed in die main UI. More specific I'm importing an UI changing theme that will take a while (2-3 secounds) to perform it's changes.
However, it's no problem if the UI is blocked while it's loading but to instruct the user to wait I want to display an WaitForm that is beeing closed if the UI Thread has finished the loading process.
Here's how I thought it might work:
static class WaitDialogManager
{
private static bool _isVisible { get; set; } = false;
private static Task _task;
public static void ShowDialog()
{
if (!_isVisible)
{
_task = Task.Factory.StartNew(() =>
{
WaitDialog _dialog = new WaitDialog();
_isVisible = true;
_dialog.Show();
do
{
System.Threading.Thread.Sleep(10);
} while (_isVisible);
_dialog.Hide();
_dialog.Dispose();
_dialog = null;
});
}
}
public static void CloseDialog()
{
_isVisible = false;
}
}
private void UpdateUI()
{
WaitDialogManager.ShowDialog();
SetUpUI();
WaitDialogManager.CloseDialog();
}
Unfortunately it seems like the different thread has no effect?! Form is beeing loaded and showed but all winforms consits of white rectangles and no marquee progress bar is beeing showed.
Why is this? And how can I change the construct to make it work?
Thanks in advance, ADP
As stated above creating a thread is a stupid idea! (Thanks to those giving people minus just because they are beginners and lack some knowlegde on different topics - as it's normal for beginners. Thank god there are somewhere such perfect people who never had to lern and knew everything from the moment of birth...)
However, a simple Refresh call ensured the form to be visible. It's doing exactly what I needed.
Related
I have spent a few days now finding a bug that freezes my companies application. The dreaded UserPreferenceChanged UI freeze. It's not a complicated bug, but hard to find in a rather big application. There are quite a few articles about how this bug unfolds but not on how to put ones finger on the faulty code. I have put together a solution, in form of a logging mechanism from multiple older tickets and (i hope) improved a bit upon them. May it save some time for the next programmer with this problem.
How to recognize the bug?
The application freezes completely. Nothing more to be done than create a memory dump and then close it via TaskManager. If you open the dmp file in VisualStudio or WinDbg you might see a stack trace like this one
WaitHandle.InternalWaitOne
WaitHandle.WaitOne
Control.WaitForWaitHandle
Control.MarshaledInvoke
Control.Invoke
WindowsFormsSynchronizationContext.Send
System.EventInvokeInfo.Invoke
SystemEvents.RaiseEvent
SystemEvents.OnUserPreferenceChanged
SystemEvents.WindowProc
:
The important two lines here are "OnUserPreferenceChanged" and "WindowsFormsSynchronizationContext.Send"
What's the cause?
SynchronizationContext was introduced with .NET2 to generalize thread synchronization. It gives us methods like "BeginInvoke" and such.
The UserPreferenceChanged event is rather self explanatory. It will be triggered by the user changing his background, logging in or out, changing the Windows accent colors and lots of other actions.
If one creates a GUI control on a background thread a WindowsFormsSynchronizationContext is installed on said thread. Some GUI controls subscribe to the UserPreferenceChanged event when created or when using certain methods. If this event is triggered by the user the main thread sends a message to all subscribers and waits. In the described scenarion: a worker thread without a message loop! The application is frozen.
To find the cause of the freeze can be especially hard because the cause of the bug (creation of GUI element on a background thread) and the error state (application frozen) can be minutes apart. See this really good article for more details and a slightly different scenario. https://www.ikriv.com/dev/dotnet/MysteriousHang
Examples
How can one provoke this error for testing purposes?
Example 1
private void button_Click(object sender, EventArgs e)
{
new Thread(DoStuff).Start();
}
private void DoStuff()
{
using (var r = new RichTextBox())
{
IntPtr p = r.Handle; //do something with the control
}
Thread.Sleep(5000); //simulate some work
}
Not bad but not good either. If the UserPreferenceChanged event gets triggered in the few milliseconds you use the RichTextBox your application will freeze. Could happen, not very likely though.
Example 2
private void button_Click(object sender, EventArgs e)
{
new Thread(DoStuff).Start();
}
private void DoStuff()
{
var r = new RichTextBox();
IntPtr p = r.Handle; //do something with the control
Thread.Sleep(5000); //simulate some work
}
This is bad. The WindowsFormsSynchronizationContext gets not cleaned up because the RichTextBox does not get disposed. If the UserPreferenceChangedEvent occures while the thread lives your application will freeze.
Example 3
private void button_Click(object sender, EventArgs e)
{
Task.Run(() => DoStuff());
}
private void DoStuff()
{
var r = new RichTextBox();
IntPtr p = r.Handle; //do something with the control
}
This is a nightmare. Task.Run(..) will execute the work on a background thread on the threadpool. The WindowsFormsSynchronizationContext gets not cleaned up because the RichTextBox is not disposed. Threadpool threads are not cleaned up. This background thread now lurks in your threadpool just waiting for the UserPreferenceChanged event to freeze your application even long after your task has returned!
Conclusion: Risk is manageable when you know what you do. But whenever possible: avoid GUI Elements in a background thread!
How to deal with this bug?
I put together a solution from older tickets. Thanks very much to those guys!
WinForms application hang due to SystemEvents.OnUserPreferenceChanged event
https://codereview.stackexchange.com/questions/167013/detecting-ui-thread-hanging-and-logging-stacktrace
This solution starts a new thread that continuously tries to detect any threads which are subscribed to the OnUserPreferenceChanged Event and then provide a call stack that should tell you why that is.
public MainForm()
{
InitializeComponent();
new Thread(Observe).Start();
}
private void Observe()
{
new PreferenceChangedObserver().Run();
}
internal sealed class PreferenceChangedObserver
{
private readonly string _logFilePath = $"filePath\\FreezeLog.txt"; //put a better file path here
private BindingFlags _flagsStatic = BindingFlags.NonPublic | BindingFlags.Static;
private BindingFlags _flagsInstance = BindingFlags.NonPublic | BindingFlags.Instance;
public void Run() => CheckSystemEventsHandlersForFreeze();
private void CheckSystemEventsHandlersForFreeze()
{
while (true)
{
try
{
foreach (var info in GetPossiblyBlockingEventHandlers())
{
var msg = $"SystemEvents handler '{info.EventHandlerDelegate.Method.DeclaringType}.{info.EventHandlerDelegate.Method.Name}' could freeze app due to wrong thread. ThreadId: {info.Thread.ManagedThreadId}, IsThreadPoolThread:{info.Thread.IsThreadPoolThread}, IsAlive:{info.Thread.IsAlive}, ThreadName:{info.Thread.Name}{Environment.NewLine}{info.StackTrace}{Environment.NewLine}";
File.AppendAllText(_logFilePath, DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss") + $": {msg}{Environment.NewLine}");
}
}
catch { }
}
}
private IEnumerable<EventHandlerInfo> GetPossiblyBlockingEventHandlers()
{
var handlers = typeof(SystemEvents).GetField("_handlers", _flagsStatic).GetValue(null);
if (!(handlers?.GetType().GetProperty("Values").GetValue(handlers) is IEnumerable handlersValues))
yield break;
foreach(var systemInvokeInfo in handlersValues.Cast<IEnumerable>().SelectMany(x => x.OfType<object>()).ToList())
{
var syncContext = systemInvokeInfo.GetType().GetField("_syncContext", _flagsInstance).GetValue(systemInvokeInfo);
//Make sure its the problematic type
if (!(syncContext is WindowsFormsSynchronizationContext wfsc))
continue;
//Get the thread
var threadRef = (WeakReference)syncContext.GetType().GetField("destinationThreadRef", _flagsInstance).GetValue(syncContext);
if (!threadRef.IsAlive)
continue;
var thread = (Thread)threadRef.Target;
if (thread.ManagedThreadId == 1) //UI thread
continue;
if (thread.ManagedThreadId == Thread.CurrentThread.ManagedThreadId)
continue;
//Get the event delegate
var eventHandlerDelegate = (Delegate)systemInvokeInfo.GetType().GetField("_delegate", _flagsInstance).GetValue(systemInvokeInfo);
//Get the threads call stack
string callStack = string.Empty;
try
{
if (thread.IsAlive)
callStack = GetStackTrace(thread)?.ToString().Trim();
}
catch { }
yield return new EventHandlerInfo
{
Thread = thread,
EventHandlerDelegate = eventHandlerDelegate,
StackTrace = callStack,
};
}
}
private static StackTrace GetStackTrace(Thread targetThread)
{
using (ManualResetEvent fallbackThreadReady = new ManualResetEvent(false), exitedSafely = new ManualResetEvent(false))
{
Thread fallbackThread = new Thread(delegate () {
fallbackThreadReady.Set();
while (!exitedSafely.WaitOne(200))
{
try
{
targetThread.Resume();
}
catch (Exception) {/*Whatever happens, do never stop to resume the target-thread regularly until the main-thread has exited safely.*/}
}
});
fallbackThread.Name = "GetStackFallbackThread";
try
{
fallbackThread.Start();
fallbackThreadReady.WaitOne();
//From here, you have about 200ms to get the stack-trace.
targetThread.Suspend();
StackTrace trace = null;
try
{
trace = new StackTrace(targetThread, true);
}
catch (ThreadStateException) { }
try
{
targetThread.Resume();
}
catch (ThreadStateException) {/*Thread is running again already*/}
return trace;
}
finally
{
//Just signal the backup-thread to stop.
exitedSafely.Set();
//Join the thread to avoid disposing "exited safely" too early. And also make sure that no leftover threads are cluttering iis by accident.
fallbackThread.Join();
}
}
}
private class EventHandlerInfo
{
public Delegate EventHandlerDelegate { get; set; }
public Thread Thread { get; set; }
public string StackTrace { get; set; }
}
}
Attention
1)This is a very ugly hack. It deals with threads in a very invasive way. It should never see a live customer system. I was already nervous deploying it to the customers test system.
2)If you get a logfile it might be very big. Any thread might cause hundreds of entries. Start at the oldest entries, fix it and repeat.(Because of the "tainted thread" scenario from Example 3 it might also contain false positives)
3)I am not sure about the performance impact of this hack. I assumed it would be very big. to my surprise it was almost not noteable. Might be different on other systems though
I recently added a login form to my application. This form shows prior to a splash screen that is shown while the main application form is loaded and various IO objects are instantiated.
Prior to the login form this is how my Program.cs would start the application
if (mutex.WaitOne(TimeSpan.Zero, true))
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
SplashScreen.ShowSplashScreen();
Application.Run(MainForm.Instance);
mutex.ReleaseMutex();
}
With the new login for the application is now started like so
if (mutex.WaitOne(TimeSpan.Zero, true))
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
UserSessionSelection ussDialog = new UserSessionSelection();
if (ussDialog.ShowDialog() == DialogResult.OK)
{
SplashScreen.ShowSplashScreen();
Application.Run(MainForm.Instance);
}
mutex.ReleaseMutex();
}
Here is the SplashScreen class
public partial class SplashScreen : Form
{
public static SplashScreen Instance { get { return lazyInstance.Value; } }
private static readonly Lazy<SplashScreen> lazyInstance =
new Lazy<SplashScreen>(() => new SplashScreen());
private SplashScreen()
{
InitializeComponent();
CenterToScreen();
TopMost = true;
}
static public void NewLoadingUpdate(String message, int percent)
{
NewUpdateDelegate nud = new NewUpdateDelegate(NewLoadingUpdateInternal);
SplashScreen.Instance.Invoke(nud, new object[] { message, percent });
}
static private void NewLoadingUpdateInternal(String message, int percent)
{
SplashScreen.Instance.lblLoadingText.Text = message;
SplashScreen.Instance.pgProgress.Value = percent;
}
private delegate void NewUpdateDelegate(String message, int percent);
private delegate void CloseDelegate();
static public void ShowSplashScreen()
{
Thread thread = new Thread(new ThreadStart(SplashScreen.ShowForm));
thread.IsBackground = true;
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
static private void ShowForm()
{
Application.Run(SplashScreen.Instance);
}
static public void CloseForm()
{
SplashScreen.Instance.Invoke(new CloseDelegate(SplashScreen.CloseFormInternal));
}
static private void CloseFormInternal()
{
SplashScreen.Instance.Close();
}
}
The error specifically happens with ShowForm the specific text is
An unhandled exception of type 'System.InvalidOperationException'
occurred in System.Windows.Forms.dll
Additional information: Cross-thread operation not valid: Control
'SplashScreen' accessed from a thread other than the thread it was created on.
The error only happens about 1/20 times when the application starts. I never encountered it prior to the login form.
Any ideas as to what causes this?
EDIT: For those late to the party, I think this SO question will help. Wait for a thread to actually start in c#
You need to create the SplashScreen on the same thread where you're using it.
But wait, that's what I'm doing, isn't it? Well, no - you're seeing a quite typical race condition.
The core of your problem, I suspect, is using Lazy to initialize the splash screen, combined with not waiting for the form to be created in your ShowSplashScreen method. In your main form, you refer to SplashScreen.Instance. Now, if the first thread that tried to read the instance is your splashscreen message loop, you're fine - that's the 19 in 20.
However, it's perfectly possible that the main UI thread gets there first - you don't block in ShowSplashScreen. In that case, the splash screen is created on the main UI thread, and you're in trouble - and good thing you're not using InvokeRequired, because that would have hidden the error even further.
Why does this have anything to do with the new login form? Well, I suspect that it's a timing thing, really - your code is broken with or without the login form. However, ShowDialog starts a new message loop, similar to Application.Run. This also means that a synchronization context has to be created - something that would otherwise only happen on your Application.Run(MainForm.Instance) line. The key point is that you've managed to make your race condition much wider - there is no longer as much time between the ShowSplashScreen call and the first time the splash screen is accessed in MainForm - and the result is BOOM.
Do not allow the ShowSplashScreen method to return until the instance is properly created, and you'll be fine. Multi-threading is hard - don't try to guess your way around. A good starting point would be http://www.albahari.com/threading/ - make sure you pay plenty of attention to proper synchronization and signalling.
UpDate1:
More detail: Thread 1 and 2 must be continuously active. Thread 1 is updating its GUI and doing HTTP POSTs. Thread 2 is using HTTPListener for incoming HTTP POSTs, and supplying that data to Thread 1. So the GUI needs to be display with current Textbox values and updated when Thread 2 supplies the data. Will Servy's or another approach allow both Threads to do their work concurrently? It appears the main thread waits for Thread 2 to complete it's work. It then takes the prepWork and does work with it. I coded in Servy's example but I couldn't find a definition for Run() with the Task class. It's library has no such method. I'm using Net 4.0 on VS 2010. Is there an equivalent method to use? Start() didn't compile either and I understand you can only run the Task once. Thanks for any additional assistance you can share.
Original Question:
I've tested code that will successfully kick off my event and update my GUI textbox in an event handler if the event is kicked off in what I understand as the UI Thread 1. When I attempt to call a Thread 1 method Fire() from my independent Thread 2 method PrepareDisplay(), Fire() is called and in turns fires off the event. I put in some Thread-safe call code (modeled from MSDN tutorial on Thread-Safety in WinForms), but the event handler still doesn't update the Textbox. When stepping thru the code, it appears that the InvokeRequired is false. My eventual goal is to pass data from Thread 2 to UI Thread 1 and update the Textboxes with the new data. I don't understand why the Thread-safe code isn't enabling this. Can someone help me understand this better, and what I have neglected? Below is the code:
Thank you very much,
namespace TstTxtBoxUpdate
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Aag_PrepDisplay aag_Prep1 = new Aag_PrepDisplay();
Thread AagPrepDisplayThread = new Thread(new ThreadStart(aag_Prep1.PrepareDisplay));
AagPrepDisplayThread.Start();
while(!AagPrepDisplayThread.IsAlive)
;
Thread.Sleep(1000);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new SetOperation());
}
}
}
namespace TstTxtBoxUpdate
{
// Thread 1: UI
public partial class SetOperation : Form
{
private string text;
public event Action<object> OnChDet;
delegate void SetTextCallback(string text);
private Thread demoThread = null;
public SetOperation()
{
InitializeComponent();
OnChDet += chDetDisplayHandler;
}
public void FireEvent(Aag_PrepDisplay aagPrep)
{
OnChDet(mName);
}
private void chDetDisplayHandler(object name)
{
this.demoThread = new Thread(new ThreadStart(this.ThreadProcSafe));
this.demoThread.Start();
}
private void ThreadProcSafe()
{
this.SetText("402.5");
}
private void SetText(string text)
{
if(this.actFreqChan1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.actFreqChan1.Text = text;
}
}
}
}
namespace TstTxtBoxUpdate
{
// Thread 2: Data prepare
public class Aag_PrepDisplay
{
#region Fields
private Aag_PrepDisplay mAagPrep;
#endregion Fields
#region Properties
public Aag_PrepDisplay AagPrepDisp;
public Aag_PrepDisplay AagPrep
{
get { return mAagPrep; }
set { mAagPrep = value; }
}
#endregion Properties
#region Methods
public void PrepareDisplay()
{
mAagPrep = new Aag_PrepDisplay();
SetOperation setOp1 = new SetOperation();
setOp1.FireEvent(mAagPrep); // calls Thread 1 method that will fire the event
}
#endregion Methods
}
}
You're getting to the point of calling InvokeRequired when your main thread is still on Thread.Sleep. It hasn't even gotten to the point of creating a message loop yet (which is one in Application.Run) so there is no message loop for Invoke to marshal a call to.
There are all sorts of issues here. You're creating multiple instance of your form, one that you show, and an entirely different form that you're setting the text of. You clearly did not intend to do this; you want to have a single form that you're setting the text for.
Your main thread should not be doing a busywait until your first thread finishes. It likely shouldn't be there at all. If it weren't for the fact that your new thread is creating yet another new thread, the fact that your main thread is blocking until the second thread finishes and the second thread is trying to marshall a call to the main thread, it would normally deadlock. You shouldn't really be creating a second new thread here at all, but this is a case of two bugs "cancelling each other out". It prevents the deadlock, but both are still incorrect, and inhibit your ability to get to a working solution.
You also shouldn't have the Thread.Sleep in the main thread at all. I have no idea what purpose that's trying to achieve.
If you're goal is simply to start some long running work before showing the first form and then to update that form when you have your results, you're doing way more work than you need to do.
To do this we can have our form accept a Task in its constructor representing the completion of the long running work. It can add a continuation to that task to set a label, or a textbox, or do...whatever, with the results of that Task.
public class SetOperation : Form
{
private Label label;
public SetOperation(Task<string> prepWork)
{
prepWork.ContinueWith(t =>
{
label.Text = t.Result;
}, TaskScheduler.FromCurrentSynchronizationContext());
}
}
Then the main thread simply needs to start a new Task to do the given work in a thread pool thread and pass that in to our form:
[STAThread]
static void Main()
{
Task<string> prepWork = Task.Run(() => DoWork());
Application.Run(new SetOperation(prepWork));
}
private static string DoWork()
{
Thread.Sleep(1000);//placeholder for real work
return "hi";
}
And we're done. Note that DoWork should probably be in its own class designed for handling your business logic; it probably shouldn't be stuck into the Program class.
I created a WPF application and then converted it into a DLL by removing the app.xaml and setting the build to Class Library. I'm using a C# Windows Forms to test the DLL. The WPF DLL is preloaded so it can later on be called to show and display instantly without having to wait for a load. What I am trying to accomplish is to call the Show(ShowWPFApp) and have the code wait until a boolean is flipped by calling WPFAppResponse (this action is passed in via the initial load). The way I have it right now causes the UI to freeze up. Any idea on how i can get it to wait without the UI freezing up?
Windows Form calling WPF DLL
namespace WindowsFormsDLLTest
{
public partial class Form1 : Form
{
WPFDLL.LoadWPFApp wpfApp = null;
public Form1()
{
InitializeComponent();
}
private void btnLoadWPFApp_Click(object sender, EventArgs e)
{
wpfApp = new WPFDLL.LoadWPFApp();
try
{
wpfApp.Load();
}
catch (Exception ex)
{
}
}
private void btnShowWPFApp_Click(object sender, EventArgs e)
{
try
{
string result = null;
result = wpfApp.ShowWPFApp("John Doe");
}
catch (Exception ex)
{
}
}
}
}
WPF DLL Application
namespace WPFDLL
{
public class LoadWPFApp
{
private Application application = null;
private MainWindow mainWindow = null;
private bool waitOnShowWindow {get; set;}
private string returnResults = null;
public void Load()
{
StartLoadingWPFApp();
}
[STAThread]
private void StartLoadingWPFApp()
{
application = new Application();
SplashScreenWindow splashWindow = new SplashScreenWindow();
splashWindow.WindowStartupLocation = WindowStartupLocation.CenterScreen;
splashWindow.Show();
try
{
mainWindow = new MainWindow(WPFAppResponse);
mainWindow.WindowStartupLocation = WindowStartupLocation.CenterScreen;
splashWindow.Close();
application.Run();
}
catch (Exception ex)
{
splashWindow.Close();
MessageBox.Show("Error starting application:" + Environment.NewLine + ex.ToString(), "WPF App Error", MessageBoxButton.OK, MessageBoxImage.Error);
mainWindow = null;
}
}
public string ShowWPFApp(string person)
{
returnResults = null;
mainWindow.LoadPerson(person);
mainWindow.Show();
while(waitOnShowWindow)
{
//Code waits until bool is set to false
}
return returnResults;
}
public void WPFAppResponse(string person)
{
returnResults = person;
waitOnShowWindow = false;
mainWindow.Hide();
}
}
}
Launching a WPF app from Windows forms is messy. You have stumbled onto a rather complex threading problem. The general recommendation is to instead create that WPF application as a WPF control library. However, I see that this may not resolve the slow loading issue that you have, which is why you made the lightweight WinForms wrapper app.
The problem is your loop:
while(waitOnShowWindow)
{
//Code waits until bool is set to false
}
That loop is running on the UI thread, blocking it from processing windows messages. (If that concept is new to you, go look it up as it is important for Windows UI stuff.) For the UI to respond, it must be running a Windows message loop. I see two solutions:
Create your own message loop.
Return immediately, then get the result later.
Solution 1:
To create your own message loop, you need to use something like Dispatcher.Run() or Dispatcher.PushFrame(). Try this and see if it works:
public string ShowWPFApp(string person)
{
returnResults = null;
mainWindow.LoadPerson(person);
mainWindow.Show();
Dispatcher.CurrentDispatcher.Run();
// do whatever to get the results
return returnResults;
}
If that doesn't work, you might need to use PushFrame() instead. Here are some more in depth articles on that topic in case Dispatcher.Run() doesn't work.
http://www.codeproject.com/Articles/152137/DispatcherFrame-Look-in-Depth
http://reedcopsey.com/2011/11/28/launching-a-wpf-window-in-a-separate-thread-part-1/
Solution 2:
public void ShowWPFApp(string person)
{
returnResults = null;
mainWindow.LoadPerson(person);
mainWindow.Show();
}
Now, the call will not block the UI thread and the WPF window will appear. The windows forms application is running a message loop, so the WPF can now run. But how do you get the result!? Since it is now running asynchronously, you will have to find some other way to get the return value back. I think an event on the MainWindow class would be the easiest way.
Also: That [STAThread] attribute isn't doing anything. [STAThread] only has meaning on the entry point of the app. Fortunately, your Windows forms app already puts [STAThread] on the Main() method, so your thread is an STA thread.
A work around could be using
await Task.Delay(1000);
inside your while loop. This might delay every run of your while loop and the UI will not freeze up. Am not sure if this would work for your case. Try and let me know. Hope this helps.
You will need to give execution back to the UI thread's event loop so that the UI doesn't freeze up. In a forms app, you can do this as follows:
while(waitOnShowWindow)
{
//Code waits until bool is set to false
System.Windows.Forms.Application.DoEvents();
}
Edit:
As Pointed out by Eric, there are potential problems with using DoEvents(), so don't go wild with it. See How to use DoEvents() without being "evil"?.
In a test app like this, it allows the code to work. However, a better solution would be to re-structure the application so that the call is unnecessary, using multi-threading if needed.
Our program works fine, until someone locks the computer or the screen-saver pops up (but not ctrl+alt+delete). Once the computer is unlocked/the screen saver is closed, the application stops drawing everything except the title bar, and stops responding to input - it displays a mostly-white window which can't be moved or closed.
(Example of application freezing - the mountains are from my desktop background)
If we let it sit for about 5~10 minutes, it comes back to life, and doesn't hang again (even after locking the computer/screen saver popup) until the application is restarted.
It's difficult to debug, because it doesn't happen when the program is started from Visual Studio, only when the .exe is manually opened.
It only happens when the splash-screen is shown - if I remove the code to show the splash-screen, it stops happening. We need the splash-screen, however.
I've tried every suggestion on this page; the only one this doesn't happen with is using Microsoft.VisualBasic.WindowsFormsApplicationBase, but that causes all sorts of other problems.
Information about this on the Internet appears to be scarce - has anyone run into a similar problem before?
Here is the relevant code:
//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();
}
}
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
//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;
}
Splash Screen Issues
The DoEvents thing is very undesirable and doesn't necessarily accomplish what you think it does. DoEvents tell the CLR to attend to the windows message loop (for the splash screen), but doesn't necessarily offer up any processing time to other threads. Thread.Sleep() will offer other threads a chance to process, but won't necessarily allow the windows message loop for your splash screen to continue pumping messages. So you really need both if you must use a loop, but in a minute I'm going to recommend getting away from this loop altogether. In addition to that loop issue, I don't see any explicit way the splash thread is being cleaned up. You need some kind of Thread.Join() or Thread.Abort() happening somewhere.
Instead of using a Application.DoEvents() loop, I like to use a ManualResetEvent to synchronize the splash forms start up with the calling thread. That way the ShowSplash() method doesn't return until the splash is shown. Anytime after that we are obviously ok to close it down as we know it was finished being shown.
Here's a thread with a few good examples:.NET Multi-threaded Splash Screens in C#
Here's how I modified my favorite example, that #AdamNosfinger posted, to include a ManualResetEvent to synchronize the ShowSplash method with the splash screen thread:
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();
}
}
Main Form Issues
It looks as though you are launching your mainform from your login window using ShowDialog and then closing the login form. Have I understood correctly? This is not good if so. ShowDialog is intended for child windows of your application and wants to have an owner window, if you don't specify an owner form in the method arguments the currently active window is assumed to be the owner. See MSDN
So your main form is assuming the login form is its parent, but you close the login form shortly after showing the main form. So I'm not sure what state the application is left in at that point. You should consider using a standard Form.Show() method instead and simply adjusting the Form properties to appear like a dialog if this is the desired outcome (ex: BorderStyle, MaximizeBox, MinimizeBox, ControlBox, TopMost).
IMPORTANT EDIT: Ok I'm human, I messed up and forgot ShowDialog was a blocking method. While that does negate the owner handle issue, I still recommend not using ShowDialog for your main application form unless you can provide a significant justification for it that is not appearance or threading related (as those should be fixed with other techniques). The advice is still sound, despite the misstep on my part.
Possible Painting Issues
You did not specify which controls you were using or if you were doing any custom painting in your application. But you need to keep in mind some windows handles will be forcibly closed when you lock the computer. For example if you have some custom painted controls and are caching fonts, brushes or other GDI resources you need to have some try { ... } catch { ... } blocks in your code that dispose of and then rebuild the cached GDI resources when an exception is raised during painting. I've run into this before where I was custom painting a list box and caching some GDI objects. If you have any custom painting code anywhere in your app, including in the splash screen, please double check all GDI objects are nicely disposed/cleaned up.
After adding a few lines of code to the code snippets above, I could compile a working program. However, I could not reproduce the problem (Windows 7 Starter). I tried locking the computer, and starting the screen saver, too. I did this while the splash screen was active, and in other situations, but the main window always remained responsive. I think there must be something else going on here, probably during the initialization of the main window.
Here is the code, maybe it helps the others figure out the problem.
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);
}
}
Several years later (with the code no longer in front of me), I'll add an answer for anyone else who experiences this problem.
The issue turned out to be exactly as Hans Passant had guessed. The problem was that, due to some incredibly obscure and innocuous bugs in the .Net framework, InvokeRequired can sometimes return false when it should return true, causing code that should run on the GUI thread to run in the background (which, due to some more obscure and innocuous bugs, causes the behavior I was seeing).
The solution is to not rely on InvokeRequired, using a hack similar to this:
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
This solution, as well as an extremely in-depth look at the causes of the issue, was found here.
since there is no working example
can you try removing Application.DoEvents(); and inserting a thread.sleep?
Application.DoEvents(); let say can be very evil.
From the quick scan I did of your code, it looks like the key to your problem might be using
Application.Run(_splashForm);
Ideally you would use that inside a thread, but maybe it would work in conjunction with your DoEvents too. Sorry if you are doing that and I just missed it...
In our application we had some similar problems with the splash screen. We wanted to have a splash screen with an animated gif (don't blame on me, it was a management decision). That only works correctly, when the splashScreen has its own message loop. Because I think the DoEvents is the key to your problem, I show you, how we solved it. Hopefully it will help you to solve your problem!
We're going to show the splash screen in that way:
// 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));
The splash screen is a simple containing the animated gif of a clock. It doesn't have any loop, so it doesn't steel any time.
When the splash needs to be closed, we do it in that way:
if (splashScreen != null)
{
if (splashScreen.IsHandleCreated)
{
try
{
splashScreen.Invoke(new MethodInvoker(() => splashScreen.Close()));
}
catch (InvalidOperationException)
{
}
}
splashScreen.Dispose();
splashScreen = null;
}
remove this line, you don't need it, You are forcing it to a single thread when the default is mta. Take the default.
loadingFormThread.SetApartmentState(ApartmentState.STA);
change the following:
using(SplashForm splashForm = new SplashForm())
{
splashForm.Show();
while(_showSplash)
Application.DoEvents();
splashForm.Close();
}
to:
SplashForm splashForm = new SplashForm())
splashForm.Show();
Change this:
public static void CloseSplashScreen()
{
_showSplash = false;
}
to this:
public static void CloseSplashScreen()
{
splashForm.Close();
}
Here's a shot in the dark: when we idle, we also ask the thread to go to sleep. I'm not sure that this will help, but it's worth a shot:
while(_showSplash) {
System.Threading.Thread.Sleep(500);
Application.DoEvents();
}
Have you tried using a WaitHandle for showing the form in the thread?
Something like:
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();
}
I think your problem is because you are using Form.ShowDialog, not Application.Run. ShowDialog runs a restricted message loop that runs on top of the main message loop and ignores some windows messages.
Something like this should work:
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();
}
}