Abby
Abby

Reputation: 13

Cross-thread operation not valid...Invoke and delegate seems useless

I have a issue with thread, I've searched for a few days but still cannot solve it..

Due to some reason, I customize a progress form and use it in threads.

I tried to write all functions inside the progress form so that they are wrapped by Invoke and delegate. Unfortunately, this code is not working properly since this.InvokeRequired is returning false when I expected it to return true.

The problem is, when I execute the program, sometimes it throw an exception: Cross-thread operation not valid: Control 'FormProgress' accessed from a thread other than the thread it was create on.

Here's the code of progress form. I've wrapped all functions with Invoke and delegate.

public partial class FormProgress : Form
{
    public FormProgress()
    {
        InitializeComponent();
    }

    public void SetStatusLabelText(string text)
    {
        if (this.InvokeRequired)
        {
            this.Invoke((MethodInvoker) delegate
            {
                label1.Text = text;
            });
        }
        else
        {
            // exception thrown here
            label1.Text = text;
        }
    }

    public void SetDialogResult(DialogResult dialogResult)
    {
        if (this.InvokeRequired)
        {
            this.Invoke((MethodInvoker)delegate
            {
                if (DialogResult == DialogResult.None)
                    this.DialogResult = dialogResult;
            });
        }
        else
        {
            if (DialogResult == DialogResult.None)
                this.DialogResult = dialogResult;
        }
    }
}

Here's the code of thread, the exception throws when I click button1

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        for (int i=0; i<100; i++)
            ProgressTest();
    }

    private void ProgressTest()
    {
        FormProgress dialog = new FormProgress();
        {
            Thread threadTest = new Thread(delegate ()
            {
                dialog.SetStatusLabelText("initial....(1)");
                Thread.Sleep(50);

                dialog.SetStatusLabelText("initial....(2)");
                Thread.Sleep(50);

                dialog.SetStatusLabelText("initial....(3)");
                Thread.Sleep(50);

                dialog.SetDialogResult(DialogResult.OK);

            });
            threadTest.Name = "ThreadTest";
            threadTest.Start();

            if (dialog.ShowDialog() == DialogResult.Cancel)
            {
                if (threadTest.IsAlive)
                    threadTest.Abort();
            }

            threadTest.Join();
        }
    }
}

Upvotes: 1

Views: 933

Answers (2)

Z.R.T.
Z.R.T.

Reputation: 1603

do changes with controls inside if (this.InvokeRequired) block

remove the else block staying after if (this.InvokeRequired)

public void SetStatusLabelText(string text)
{
    if (this.InvokeRequired)
    {
        this.Invoke((MethodInvoker) delegate
        {
            label1.Text = text;
        });
    }
}

public void SetDialogResult(DialogResult dialogResult)
{
    if (this.InvokeRequired)
    {
        this.Invoke((MethodInvoker)delegate
        {
            if (DialogResult == DialogResult.None)
                this.DialogResult = dialogResult;
        });
    }
}

let's consider method ProgressTest(), what happaning: after threadTest.Start() has been called , the threadTest method starts execution of his work item in a new thread

after dialog.ShowDialog() the GUI thread become blocked , it makes this.InvokeRequired = false at the same time threadTest keep working and when threadTest try to execute

else
{
    label1.Text = text;
}

label1.Text setter is called from NONE GUI thread (it is called from "ThreadTest" thread), that's why you get exception

It should be noted that dialog.SetStatusLabelText("initial....") which supposed to be called 300 times , actually will be called less then 300

Upvotes: -1

mjwills
mjwills

Reputation: 23820

As per the docs:

If the control's handle does not yet exist, InvokeRequired searches up the control's parent chain until it finds a control or form that does have a window handle. If no appropriate handle can be found, the InvokeRequired method returns false.

This means that InvokeRequired can return false if Invoke is not required (the call occurs on the same thread), or if the control was created on a different thread but the control's handle has not yet been created.

In the case where the control's handle has not yet been created, you should not simply call properties, methods, or events on the control. This might cause the control's handle to be created on the background thread, isolating the control on a thread without a message pump and making the application unstable.

You can protect against this case by also checking the value of IsHandleCreated when InvokeRequired returns false on a background thread. If the control handle has not yet been created, you must wait until it has been created before calling Invoke or BeginInvoke. Typically, this happens only if a background thread is created in the constructor of the primary form for the application (as in Application.Run(new MainForm()), before the form has been shown or Application.Run has been called.

The issue you have is that some of your InvokeRequired calls may be occurring before the form has been shown. This is because you are starting your new thread before calling dialog.ShowDialog(). Note, as is common with race conditions, the problem won't always occur - just sometimes.

As per above, you may want to consider checking IsHandleCreated before executing the logic in your else blocks (after checking InvokeRequired) to protect against this possibility. Alternatively, rethink your entire strategy around the progress form.

Upvotes: 4

Related Questions