Solem
Solem

Reputation: 213

Form "Not responding" if it has not been shown before

I have an application that watches a directory for changes. When a file (a log file) is created, it analyses its contents, writes the results in a form (that exists already and has been initialized, although may be currently hidden) and finally shows this form to the user.

When the application starts, just an icon is shown in the task bar. The main method just creates the icon in the task bar and initializes the class that watches/analyses and controls the form with the results.

public static void Main(string[] args) {
    NotificationIcon notificationIcon = new NotificationIcon();
    notificationIcon.notifyIcon.Visible = true;
    if (notificationIcon.Init()) {
        MainForm = ResultForm.GetInstance();
        Application.Run();
    }
}

"ResultForm" is the class I just mentioned before and has the following methods relevant to the problem:

public static ResultForm GetInstance() {
    // _this is an attribute from the class. Is used to work always
    // with just one instance of the classe
    if (_this==null)
        _this= new ResultForm();

    return _this;
}

private ResultForm() {
    // initialization of the GUI form
    InitializeComponent();

    [...]

    // watcher for the log files
    logsWatcher= new FileSystemWatcher(LOGFILES_FOLDER);
    logsWatcher.Created += new FileSystemEventHandler(NewFile);
    logsWatcher.EnableRaisingEvents=true;
    logsWatcher.SynchronizingObject = this;
}

private void NewFile (object source, FileSystemEventArgs e) {
    // make sure the file is of the correct type
    [...]
    // perform some analysis on the file
    [...]
    // update the contents in the form (some TreeViews and labels)
    [...]

    // show the form to the user
    _this.Show();
}

And now comes the problem. If the application starts, a file is analysed and the main form has not been shown yet, it will be shown as "Not responding" when the analysis is complete, although everything has been already done. If a new file is created after, it will be successfully analysed, although the form will stay in this "Not responding" state.

HOWEVER, if the form has been opened at least once before since the application started (say, you double-clicked on the icon, the form was shown and you closed it (or left it open, it does not matter)), everything will work smoothly.

As a workaroud, I can modify the main with the following two lines (before the Run() method), so the form will be shown at least once before any file comes:

MainForm.Show();
MainForm.Hide();

(I hide it back because it should not be visible until an analysis has been performed or the user has explictly clicked on the icon.) Other than that, there is no difference in the program: the work done is the same and the form is always shown once everything is done. I have made sure with the debugger that the end of the method is reached during execution.

How can I solve this problem without the mentioned workaround?

I have tried creating a thread for the analysis, using Application.DoEvents() or blocks of code similar to this one. In the best of the cases, the form shows all its contents properly, but stays in a "Not responding" state. I have also tried to just leave the Show() call in the method with exactly the same results, which tells me it is not a problem of heavy load, but of something I may be doing wrong.

EDIT Since @thecoon requested it, I have uploaded a small project that reproduces the problem. It has been done with SharpDevelop, in case you also use it. --> http://dl.dropbox.com/u/1153417/test.zip

There is a small explanation in the Main() method.

Upvotes: 3

Views: 1106

Answers (1)

dash
dash

Reputation: 91462

I strongly suspect this is a threading issue, one caused by how you start your ResultForm, and also how you've set the FileSystemObjects synchronization object.

The synchronization object effectively chooses the thread to marshall the updates on. In this instance, it's also the thread you are trying to show the GUI on, so you might have run into a good old blocking operation, or there may just be a large number of file system events causing your thread to context switch back and forth rapidly.

As a starter, try this instead:

public static void Main(string[] args) {
    NotificationIcon notificationIcon = new NotificationIcon();
    notificationIcon.notifyIcon.Visible = true;
    if (notificationIcon.Init()) {
        MainForm = new ResultForm();
        Application.Run(MainForm);
    }
}

Note that we now marshal the ResultForm to the UI thread directly.

And change ResultForm this way (there's no real need for it to be a singleton, too):

public ResultForm() {
    // initialization of the GUI form
    InitializeComponent();

    [...]
    this.Load += ResultForm_Load;
}


protected void ResultForm_Load(object sender, EventArgs e)
{
    // watcher for the log files
    logsWatcher= new FileSystemWatcher(LOGFILES_FOLDER);
    logsWatcher.Created += new FileSystemEventHandler(NewFile);
    logsWatcher.EnableRaisingEvents=true;
    //Don't set the synchronization object - now all events from the FileSystemWatcher will be marshalled on a background thread
    Visible = false; //Hide the form if you want or minimize to tray or similar.
}

private void NewFile (object source, FileSystemEventArgs e) {

    if(InvokeRequired){
        //Ensures the file system events are marshalled back to the GUI thread
        Invoke(new MethodInvoker(() => {NewFile(source, e);}));
        return;
    }

    // make sure the file is of the correct type
    [...]
    // perform some analysis on the file
    [...]
    // update the contents in the form (some TreeViews and labels)
    [...]

    // show the form to the user
    Show(); //or Visible = true;
}

By not setting the synchronization object on the FileSystemWatcher to be the form, you ensure that all File System event marshalling will happen in ThreadPool threads. When a New event is raised, we just remember to marshal back to the UI thread by checking InvokeRequired and calling the form's Invoke method if necessary.

UPDATE

The main reason is that, by calling either MainForm.Show directly or via Application.Run(MainForm), you push the form onto the thread the message loop is on.

If you run the app using your original code, when NewFile is called, Application.MessageLoop is false.

If you use your workaround, or the standard way of showing the application, as per my example, Application.MessageLoop is true.

My best guess here is that the form is deadlocking because the FileSystemWatcher (which, as it's using the form as a synchronization object in the original example, which effectively means it calls BeginInvoke on the form). However, it could also be a huge variety of other issues; Form.Show() is actually hanging on the method FPushMessageLoop - it is caught in an infinite loop.

@HansPassant or @HenkHolterman have both posted extensively around these issues - see https://stackoverflow.com/a/3833002/1073107 for example. Note that if you show a splashscreen or similar on start up, then all initialization works as expected, and NewFile completes successfully; in short, it looks like you have to show something at app startup if you want your process to work. I don't think this is a bad thing; the user can see an app has started and is now running in the tray, for example, and you can be sure you wont run into this issue.

Upvotes: 1

Related Questions