views:

40

answers:

2

I've got the following situation,

Private Sub MyButton_Click(sender as Object, args as EventArgs) Handles MyButton.Click
    Me.pleaseWaitFrm = New PleaseWaitForm()
    ' Fire up new thread to do some work in (AddressOf DoMyWork)
    Me.pleaseWaitFrm.ShowDialog()
End Sub

Private Sub DoMyWork()
   Dim log = Me.DoTheActualWork()

   Me.pleaseWaitFrm.Close()

   Using logFrm as New LogViewer(log)
       logFrm.ShowDialog()
   End Using
End Sub

If the DoTheActualWork() call exits fast enough, the Me.pleaseWaitFrm.Close() call is happening during the Me.pleaseWaitFrm.ShowDialog() call. The result, no surprise, is this exception:

An unhandled exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll

Additional information: Value Close() cannot be called while doing CreateHandle().

Obviously this is the old "you cannot .Close() a WinForm while it's in the process of loading" problem. But what isn't obvious to me is how best to prevent that from happening in this case? How does one safely and reliably delay the .Close() until it is safe to do so in this situation?

+1  A: 

Why not set a "ShouldClose" flag which can be checked when it's safe to close the form - and close it if required?

With regards to a code example, I'd implement it slightly differently but let me know if this breaks any other requirements and we can modify it...

    ''in PleaseWaitForm:
    Public Property ShouldClose as boolean = false

Private Sub frmSplash_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    Me.Close()
End Sub

    ''Your posted code
    Private Sub MyButton_Click(sender as Object, args as EventArgs) Handles MyButton.Click
        Me.pleaseWaitFrm = New PleaseWaitForm()
        '' Fire up new thread to do some work in (AddressOf DoMyWork)
        Me.pleaseWaitFrm.ShowDialog()
    End Sub

    Private Sub DoMyWork()
       Dim log = Me.DoTheActualWork()


       Me.pleaseWaitFrm.ShouldClose = True
       If Me.pleaseWaitFrm.Created Then
           Me.pleaseWaitFrm.Created.Close
       End If

       Using logFrm as New LogViewer(log)
           logFrm.ShowDialog()
       End Using
    End Sub

In short, if we Can close the form, we do - otherwise, set a flag and the form will do it when it finishes loading.

I tested and didn't get any issues calling Me.Close inside the frmSplash.Load() but if you do encounter any problems, you can make it cast-iron by having a worked on frmSplash which checks the value rather than use the Load event

Edit: Spotted and fixed a bug

Basiclife
Could I see a code example of this worked into my above example? I understand what you are trying to say, but not understanding the safe and reliable implementation of that.
ckittel
Sure - one minute
Basiclife
Fixed code but I have to admit, Hans Passant's answer is superior
Basiclife
+1  A: 

Me.pleaseWaitFrm.Close()

That's an illegal call, you are not allowed to close a form from another thread. Not sure how you got away with it, that should raise an IllegalOperationException when you run with a debugger. Review your code and delete any assignment to the Control.CheckForIllegalCrossThreadCalls property.

This exception you're getting is a clear side-effect of this. You must use Control.Invoke() to get the dialog closed. This automatically solves your problem, the delegate target cannot execute until the dialog is loaded.

Leverage the BackgroundWorker class, it makes this easy. You can close the dialog in a RunWorkerCompleted event handler.

Hans Passant
Does closing the form in the RunWorkerCompleted really change anything? What if the RunWorkerCompleted is fired before the .ShowDialog() was ever called? Yes, it will be called back on the created thread (a good thing, and it will prevent the exception!), but what is to ensure that it happens AFTER the .ShowDialog()'s work is done? Seems like there is still an assumption being made that the form is actually shown by the time we get to RunWorkerCompleted. Now the form could open and never automatically close due to the same race condition.
ckittel
Yes it does. This event, like the Invoke delegate target, cannot run until the UI thread re-enters the message loop. Which won't happen until after ShowDialog() got the form loaded. Try it.
Hans Passant
Thanks. Looks like as long as I'm not MsgBox()ing or Application.DoEvents()ing between the time that RunWorkerAsync() is called and .ShowDialog() is called, I'm good to go. Calling either of those (especially MsgBox()) leads to an ObjectDisposedException on .ShowDialog().
ckittel