10

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

(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;
}
Community
  • 1
  • 1
BlueRaja - Danny Pflughoeft
  • 84,206
  • 33
  • 197
  • 283
  • 1
    Can you attach the debugger when it is locked? – Albin Sunnanbo Nov 15 '11 at 18:41
  • Can you produce a minimal working example? Do you use any p/invoke? The thing with at least the screen-saver is that it runs on a separate desktop. I think the lock screen also runt on a separate desktop. Does this give you any clues? – Albin Sunnanbo Nov 15 '11 at 18:45
  • @Albin: Yes I can attach a debugger. It only says it is hung in `MainForm.ShowDialog()`. I can't seem to produce a minimal working example. No p/invoke, at least not anywhere relevant to this. – BlueRaja - Danny Pflughoeft Nov 15 '11 at 19:06
  • Where are you showing the splash screen, in the FormLoad handler or where? Can you copy at least that code. Also, what is the purpose of the splash screen, only to hide the initialization? And splash screen is hidden when this locking is happening, correct? – zmilojko Nov 15 '11 at 19:10
  • 1
    Take a closer look where it is hung. Is it really inside `MainForm.ShowDialog` or is their something more specific further up the call stack. If it really is stuck inside `ShowDialog` then that means messages are no longer being pumped on the main UI thread. That would be very odd and could possibly indicate an obscure bug in .NET that manifests itself only when your splash screen is shown. This is a pretty odd problem. – Brian Gideon Nov 17 '11 at 21:15
  • 1
    By the way, that while loop calling `Application.DoEvents` should be spinning incessantly. Is it consuming a lot of CPU time? – Brian Gideon Nov 17 '11 at 21:18
  • @Brian: Yes, it's really stuck in `ShowDialog()`. And yes, it is taking up a lot of CPU; the old C++ Windows message-loop workaround (calling `Thread.Sleep(10)`) didn't help. But that doesn't bother me because A. it's on a background thread, so it shouldn't starve anything important, and B. the splash screen is only up for about 5 seconds anyways. – BlueRaja - Danny Pflughoeft Nov 17 '11 at 21:35
  • Look in the Output window after locking and unlocking the work station. Do you see a first chance notification for InvalidAsynchronousStateException? You should. – Hans Passant Nov 17 '11 at 23:21
  • Take a dump of the frozen process (using Windbg.exe or userdump.exe, but *NOT* Visual Studio) and then upload the dump, post here a link. – Remus Rusanu Nov 18 '11 at 00:14
  • 1
    and `_showSplash` shoudl be `volatile`, btw. – Remus Rusanu Nov 18 '11 at 00:15
  • Do you get any activate/deactivate window messages when it goes to lock? The symptoms sound similar to what you experience in a game engine when you deactivate/minimize the window and come back - it takes a long time for the simulation to catch up unless you "cut the gordian knot" and skip your simulation ahead. I wonder if the same is true for message processing here, in which case maybe there is a way to detect this case, and flush/clear the event loop in those scenarios. – Merlyn Morgan-Graham Nov 18 '11 at 02:25
  • @Hans: Er, no, I don't. What does that mean? – BlueRaja - Danny Pflughoeft Nov 18 '11 at 18:52
  • 1
    It doesn't mean anything if you don't see it. This kind of code often gets into trouble with the SystemEvents class. You can confuzzle it so it fires events on the wrong thread. – Hans Passant Nov 18 '11 at 18:57
  • @Hans: Now that you mention it, VS shows some of the freezing applications (but not all of them) having a worker thread named `.NET SystemEvents`. I have no idea where it came from. Could that be related? – BlueRaja - Danny Pflughoeft Nov 18 '11 at 19:24
  • Yes, that's the thread that SystemEvents starts to pump a message loop for the hidden notification window. Its presence does not automatically indicate trouble. Getting InvalidAsynchronousStateException from that thread does indicate trouble. But you said you're not seeing that so that theory is shot in the rear end. – Hans Passant Nov 18 '11 at 19:30
  • @BlueRaja-DannyPflughoeft When the PCs locked programs still run in the background, hence to possibly see the root cause during the unlocking (or locking) and 5-10 mins after when it recovers. Can you please add code to trace out info - call stack and timing info. Use this answers code: http://stackoverflow.com/questions/652550/how-do-i-detect-a-lock-this-computer-command-from-a-wpf-application with a splice of AOP Event monitoring from http://www.sharpcrafters.com/postsharp/documentation or use a RedGate tool, let us know if this yields any worthy results to aid diagnosing the problem:) – Jeremy Thompson Nov 22 '11 at 10:47

10 Answers10

3

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.

Community
  • 1
  • 1
BenSwayne
  • 16,810
  • 3
  • 58
  • 75
  • Please reread the question - not only did I say I've tried other methods of showing the splash-form, but I explicitly linked to that exact same thread! None of those methods solve the problem, however. It is not a custom painting issue, because the program is not getting hung-up in any user code (also mentioned above). And you seem to be confusing `Form.ShowDialog()` with `Form.Show()` - `ShowDialog()` does not return until the child-form exits, so `this.Close()` will not be called until the program is ready to exit. – BlueRaja - Danny Pflughoeft Nov 17 '11 at 22:58
  • You are correct on the ShowDialog being blocking, my bad on that one - I will edit a little correction in there. But still why use ShowDialog and why keep the login form in behind? I did read the referenced post you linked to, you'll notice I modified my favorite answer from that question to include a blocking ShowSplash function as you said you were having problems with the close being called before show. I am adding more code to what you already tried in order to fix possible ordering issues with a fast start up. – BenSwayne Nov 17 '11 at 23:39
1

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.

BlueRaja - Danny Pflughoeft
  • 84,206
  • 33
  • 197
  • 283
1

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);
  }
}
kol
  • 27,881
  • 12
  • 83
  • 120
0

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.

Fredou
  • 19,848
  • 10
  • 58
  • 113
  • As I said, I tried every other suggestion on [this page](http://stackoverflow.com/questions/48916). No method of creating the splash screen works, so it is not because of `DoEvents()`. – BlueRaja - Danny Pflughoeft Nov 17 '11 at 20:34
0

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...

Brandon Moore
  • 8,590
  • 15
  • 65
  • 120
0

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;
}
Fischermaen
  • 12,238
  • 2
  • 39
  • 56
0

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();
}
edepperson
  • 1,035
  • 1
  • 14
  • 33
0

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();
}
Graymatter
  • 6,529
  • 2
  • 30
  • 50
0

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();
    }
competent_tech
  • 44,465
  • 11
  • 90
  • 113
0

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();
    }
}
Nick Butler
  • 24,045
  • 4
  • 49
  • 70