1

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();
        }
    }
}
mjwills
  • 23,389
  • 6
  • 40
  • 63
Abby
  • 13
  • 2
  • 1
    What is it you are trying to do anyway? Spawning a thread that does nothing but immediately invoke back to the UI is pointless. Additionally, spawning a thread only to have the main thread block on it to complete sort of defeats the purpose too. Considering that the UI thread is now blocked, `Invoke` will never work as you have created a thread dead-lock. https://community.oracle.com/blogs/kgh/2004/10/19/multithreaded-toolkits-failed-dream –  Oct 30 '18 at 03:12
  • The issue is likely that you haven't shown the form yet - as per https://stackoverflow.com/questions/10733693/invokerequired-keeps-returning-false-when-true-is-expected . _This is a classic race condition - your new thread is accessing the progress form before it has been shown. Sometimes it works, since sometimes the thread accesses it **after** it has been shown._ – mjwills Oct 30 '18 at 03:26
  • @MickyD I block the main thread because I need to show a progress and wait for the thread to finish . The program executes perfectly when it goes into the if (this.InvokeRequired), but throws exception when it goes into else block. – Abby Oct 30 '18 at 03:28
  • That's not how you report progress. –  Oct 30 '18 at 03:35
  • You have to use invoke on the control or form. not the other class. – slow Oct 30 '18 at 07:49

2 Answers2

4

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.

mjwills
  • 23,389
  • 6
  • 40
  • 63
  • Thank you for your help. I learned something, and I'll rethink the strategy. – Abby Oct 30 '18 at 05:04
  • For the downvoters, please feel free to comment to let me know the error in my post. I'd love to improve it! – mjwills Oct 30 '18 at 10:01
-1

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

Z.R.T.
  • 1,543
  • 12
  • 15
  • Could you tell me why remove the if (this.InvokeRequired) block can solve the issue? – Abby Oct 30 '18 at 03:23
  • Not the downvoter, but you should use `BeginInvoke`. Invoke can lead to thread deadlock –  Oct 30 '18 at 03:23
  • @Abby this simple solution will avoid you from {"Cross-thread operation not valid: Control 'FormProgress' accessed from a thread other than the thread it was created on."} – Z.R.T. Oct 30 '18 at 03:42
  • @Z.R.T. It may be worthwhile adding a note as to _why_ it will help. Also consider - what happens if the form calls this method from the UI thread? On first reading it seems to me that if it does the method will do nothing? That does seem a little odd. – mjwills Oct 30 '18 at 03:53
  • @mjwill i post my explanation – Z.R.T. Oct 30 '18 at 05:17