views:

38072

answers:

12

I have a scenario. (Windows Forms, C#, .NET)

  1. There is a main form which hosts some user control.
  2. The user control does some heavy data operation, such that if I directly call the Usercontrol_Load method the UI become nonresponsive for the duration for load method execution.
  3. To overcome this I load data on different thread (trying to change existing code as little as I can)
  4. I used a background worker thread which will be loading the data and when done will notify the application that it has done its work.
  5. Now came a real problem. All the UI (main form and its child usercontrols) was created on the primary main thread. In the LOAD method of the usercontrol I'm fetching data based on the values of some control (like textbox) on userControl.

The pseudocode would look like this:

//CODE 1

UserContrl1_LOadDataMethod()
{

    if(textbox1.text=="MyName") <<======this gives exception
    {
        //Load data corresponding to "MyName".
        //Populate a globale variable List<string> which will be binded to grid at some later stage.
    }
}

The Exception it gave was Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on.

To know more about this I did some googling and a suggestion came up like using the following code

//CODE 2

UserContrl1_LOadDataMethod()
{
    if(InvokeRequired) // Line #1
    {

        this.Invoke(new MethodInvoker(UserContrl1_LOadDataMethod));
        return;
    }

    if(textbox1.text=="MyName") //<<======Now it wont give exception**
    {
    //Load data correspondin to "MyName"
        //Populate a globale variable List<string> which will be binded to grid at some later stage
    }
}

BUT BUT BUT... it seems I'm back to square one. The Application again become nonresponsive. It seems to be due to the execution of line #1 if condition. The loading task is again done by the parent thread and not the third that I spawned.

I don't know whether I perceived this right or wrong. I'm new to threading.

How do I resolve this and also what is the effect of execution of Line#1 if block?

The situation is this: I want to load data into a global variable based on the value of a control. I don't want to change the value of a control from the child thread. I'm not going to do it ever from a child thread.

So only accessing the value so that the corresponding data can be fetched from the database.

A: 

Controls in .NET are not generally thread safe. That means you shouldn't access a control from a thread other than the one where it lives. To get around this, you need to invoke the control, which is what your 2nd sample is attempting.

However, in your case all you've done is pass the long-running method back to the main thread. Of course, that's not really what you want to do. You need to rethink this a little so that all you're doing on the main thread is setting a quick property here and there.

Joel Coehoorn
+8  A: 

You only want to use Invoke or BeginInvoke for the bare minimum piece of work required to change the UI. Your "heavy" method should execute on another thread (e.g. via BackgroundWorker) but then using Control.Invoke/Control.BeginInvoke just to update the UI. That way your UI thread will be free to handle UI events etc.

See my threading article for a WinForms example - although the article was written before BackgroundWorker arrived on the scene, and I'm afraid I haven't updated it in that respect. BackgroundWorker merely simplifies the callback a bit.

Jon Skeet
here in this condition of mine . i m not even changing the UI. I m just accessig its current values from the child thread. any suggestion hw to implement
SilverHorse
You still need to marshal over to the UI thread even just to access properties. If your method can't continue until the value is accessed, you can use a delegate which returns the value. But yes, go via the UI thread.
Jon Skeet
Hi Jon, i belive you are heading me to the right direction. Yes i need the value without it i cant proceed further. Please could you eloborate on that ' Using a delegate which return a value'. Thanks
SilverHorse
Use a delegate such as Func<string>:string text = textbox1.Invoke((Func<string>) () => textbox1.Text);(That's assuming you're using C# 3.0 - you could use an anonymous method otherwise.)
Jon Skeet
+20  A: 

[EDIT] As per SilverHorse's update comment, the solution you want then should look like:

UserContrl1_LOadDataMethod()
{
    string name = "";
    if(textbox1.InvokeRequired)
    {
        textbox1.Invoke(new MethodInvoker(delegate { name = textbox1.text; }));
    }
    if(name == "MyName")
    {
        // do whatever
    }
}

Do your serious processing in the separate thread before you attempt to switch back to the control's thread. For example:

UserContrl1_LOadDataMethod()
{
    if(textbox1.text=="MyName") //<<======Now it wont give exception**
    {
        //Load data correspondin to "MyName"
        //Populate a globale variable List<string> which will be
        //bound to grid at some later stage
        if(InvokeRequired)
        {
            // after we've done all the processing, 
            this.Invoke(new MethodInvoker(delegate {
                // load the control with the appropriate data
            }));
            return;
        }
    }
}
Jeff Hubbard
SilverHorse, does the code work, or did you just accept this answer because it has code?
Lasse V. Karlsen
Also Jon's answer has helped me .Pls see comments in the answer.
SilverHorse
+1  A: 

You need to look at the Backgroundworker example:
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx Especially how it interacts with the UI layer. Based on your posting, this seems to answer your issues.

Pat
+1  A: 

BackgroundWorker is really nice. The callback will be made on your UI thread so you can do all of your UI updates there.

Jamie Eisenhart
A: 

I have a similar problem.

I have a UI and wanted to load data in the background thread (an event). Once data is loaded I am calling a delegate from within that event handler that is accessing the UI controls and I get this error.

Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on.

Here is the code to call event and delegate in the parameter object:

 this.View.ShowAnalysis = this.ShowAnalysis;
 if (GenerateAnalysis != null)
  GenerateAnalysis(this, new DataEventArgs<IGridRptView>(this.View));

And in the event handler I am calling the delegate:

    public void OnGenerateAnalysis(object sender, DataEventArgs<IGridRptView> e)
    {
        try
        {
            Dictionary<string, DataSet> dictData = new Dictionary<string, DataSet>(1);
            e.Data.Report.LoadData(e.Data.ReportName, ref dictData, true);
            e.Data.DictDataSets = dictData;
            e.Data.ShowAnalysis();
        }
        catch (Exception _ex)
        {
            System.Console.Write(_ex.Message);
        }
    }

The Delegate calls the method successfully but when I try add anything to the control this raises an exception.

This is the method that is called for delegate:

    public void ShowAnalysis()
    {
        Infragistics.Win.UltraWinTabControl.UltraTabStripControl _reportTabStrip = View.ReportTabStrip;
        _reportTabStrip.Tabs.Clear();
        IEnumerator enumTabs = View.Report.Tabs.GetEnumerator();
        DataSet ds = null;
        while (enumTabs.MoveNext())
        {

            TabInfo tabinfo = (TabInfo)((KeyValuePair<string,TabInfo>)enumTabs.Current).Value;
            if (View.DictDataSets.TryGetValue(tabinfo.Key, out ds))
            {
                tabinfo.Data = ds;
            }
            _reportTabStrip.Tabs.Add(tabinfo.Key, tabinfo.ToString());
        }
        View.ReportTabStrip = _reportTabStrip;            
        CancelStatusBusy(this,new EventArgs());
        CancelProgressBar(this, new EventArgs());
    }

I am looking forward to a solution.

quadri
I think I am able to resolve it.
quadri
+1  A: 

I have had this problem with the FileSystemWatcher and found that the following code solved the problem:

fsw.SynchronizingObject = this

The control then uses the current form object to deal with the events, and will therefore be on the same thread.

Cookey
This was incredibly helpful. Fixed my code in one line!
jcollum
+3  A: 

Use the code found here on StackOverflow to eliminate the need to write a new delegate every time you want to call Invoke.

CLaRGe
A: 

The cleanest (and proper) solution for UI cross-threading issues is to use SynchronizationContext, see Synchronizing calls to the UI in a multi-threaded application article, it explains it very nicely.

Igor Brejc
A: 

For anyone else that is dealing with this issue, take a look at this article.

I found it really interesting and it works quite well.

Brandon
A: 

http://javaflowswithin.blogspot.com/2010/03/c-threadsafecontrol.html

Derek White
-1: please don't answer a question with just a link. Besides, your article is silly. Are you going to override every property and method of the base class in order to play Invoke?
John Saunders
A: 

The selected solution worked and fixed my problem (the same issue as OP) perfectly.

Thanks!

asdf