views:

56

answers:

3

I am trying to display some information on a grid queried from a sql server. The data gathering can take about 10 seconds so I don't want to lock the UI thread.

I currently have code like:

ThreadPool.QueueUserWorkItem(DataUpdateThread, new UpdateParams(year));  

private struct UpdateParams
{
    internal string year;

    internal UpdateParams(string year)
    {
        this.year = year;
    }
}

private void DataUpdateThread(object state)
{
    DataTable dTable = new DataTable();
    try
    {
        this.Invoke((MethodInvoker)delegate
        {
            //stop data editing on the grid
            //scrolling marquee for user
            marquee.Visible = true;
            marquee.Enabled = true;
            grdMain.Visible = false;
            grdMain.DataSource = null;
        });

            UpdateParams parameters = (UpdateParams)state;            
            dTable = GetData(parameters.year);
    }
    catch (Exception ex)
    {
        this.Invoke((MethodInvoker)delegate
        {
            //log error + end user message
        });
    }
    finally
    {
        this.Invoke((MethodInvoker)delegate
        {
            grdMain.DataSource = dTable;
            grdMainLevel1.RefreshData();
            marquee.Visible = false;
            marquee.Enabled = false;
            grdMain.Visible = true;
        });
   }
}

This works most of the time apart from if the form it is on is closed before the update completes it will crash with the error:

Invoke or BeginInvoke cannot be called on a control until the window handle has been created.

I understand the error will be because the form no longer exists so when the finally section tries to invoke the method on the UI thread it can't.

Is there a better way to do this whole thing? I guess I can handle the invoke errors but it looks messy and I think I have probably missed a simpler way.

+2  A: 

You can check whether the form has been closed and don't do the invoke if the form has been closed. if (this.IsHandleCreated) should work. This can however still give problems because the form can be closed between the check and the call to BeginInvoke. The only 'full-proof' solution is then to enclose the entire call in a try/catch.

Pieter
or this.IsHanleCreated
Dmitry Karpezo
@Dmitry: * `IsHandleCreated`, right?
abatishchev
Yep, that should work too.
Pieter
Thanks that solved it. Out of curiosity is their likely any difference between the two properties? I guess the form handle is created when the form is and disposed of at the same time.
PeteT
You should `IsHandleCreated` because `Invoke` will only work after the form has been shown. Before the show, you get the exception too.
Pieter
This will give you a nasty hard to reproduce bug. Consider what happens when the form ceases to exist after the check passes but you are still in the if block.
DanDan
@DanDan ok I see your point do you have another suggestion?
PeteT
try/catch. That's probably the only 'full-proof' solution.
Pieter
This doesn't actually work reliably. There's an unsolvable race condition, the form could get destroyed between the calls to IsHandleCreated and Invoke(). Small odds, not zero.
Hans Passant
@Hans Passant - Yep, that's why a try/catch probably will be the only thing that's going to work reliably.
Pieter
But testing IsHandleCreated in the invoked code should be okay.
Hans Passant
It will minimize the chance of exceptions from the `BeginInvoke`, but will **not** guarantee they won't occur.
Pieter
+1  A: 

Try to use InvokeRequired() before Invoke()

abatishchev
+1  A: 

Invoke uses a special WinForms SynchronizationContext behind the scenes that you can access with SynchronizationContext.Current anywhere in your app.

CORRECTION after some poking in Reflector: actually Invoke goes the direct way of marshalling via PostMessage, it's the BackgroundWorker that makes use of SynchronizationContext behind the scenes. Invoke will throw if it does not have a window handle.

Basically you need to store it in a variable before you start the thread, e.g. while you're still in your UI thread, and use Post or Send method of the context in the thread's code. Doing so will marshal the stuff properly without window handles.

liggett78