views:

71

answers:

4

I have a worker thread that needs to add items to a BindingList. However, the BindingList is databound to a DataGridView. So, when I try to add to the list, I get an InvalidOperationException (Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on.)

Normally for this exception you would do:

if(winformControl.InvokeRequired) {
    winformControl.Invoke(MethodDelegate);
}

However, the databinding confuses things, as there is no Winform control in sight. All I have is the following line, which throws the exception:

ClassInstance.MyBindingList.Add(myObject);

If you have a solution specifically for this scenario, great.

If not, how can I get the worker thread to tell my main thread to perform a particular method (with several parameters supplied by the worker thread)? This may be a preferable option, since my worker thread is actually doing a bunch of stuff at the moment (like writing to the database), and I'm not sure if everything is thread-safe. I'm a student, and new to multithreading, and it really is not my forte yet.

A: 

You can fire an event to the main, UI, thread and there have:

if (this.InvokeRequired)
{
    this.Invoke(...);
}

so you are testing on the main Window itself.

ChrisF
Yup, this did the trick. Took a whole lot of delegates, but I got it eventually. Thanks!
recoisiche
+1  A: 

One option here is to tell BindingList<T> to use the sync-context, like this - however, this is arguably not the best approach. I wonder if you could expose your data via an event or similar (rather than adding to the list directly) - then have your UI handle the event by sending to the right thread and adding to the UI model.

Marc Gravell
I actually did try that earlier, but it just threw the exception on base.OnListChanged(e);
recoisiche
A: 

In your worker class constructor, try this:

    private System.Threading.SynchronizationContext mContext = null;

    /// <summary>
    /// Constructor for MyBackgroundWorkerClass
    /// </summary>
    public MyBackgroundWorkerClass(System.Threading.SynchronizationContext context)
    {
        mContext = context;
    }

Then, when you need to invoke something on the UI thread:

    private void CallOnTheUiThread(object dataToPassToUiThread)
    {

            // Make sure the code is run on the provided thread context.
            // Make the calling thread wait for completion by calling send instead of post.
            mContext.Send(new System.Threading.SendOrPostCallback(
                delegate(object state)
                {
                    // Change your UI here using dataToPassToUiThread.  
                    // Since this class is not on a form, you probably would 
                    // raise an event with the data.


                }
            ), null);

    }

When creating your worker class from a form on the UI thread, this is what you would pass as the synchronization context.

    private void Form1_Load(object sender, EventArgs e)
    {
        MyBackgroundWorkerClass myworkerclass = new MyBackgroundWorkerClass(System.Threading.SynchronizationContext.Current);
    }
bporter
By the way, this should still work as long as the instance of the background worker class is CREATED on the UI thread (when System.Threading.SynchronizationContext.Current is called.
bporter
A: 

BackgroundWorkers are easy to implement if you are able to given the requirements.

Define a DoWork method that runs on a background thread such as saves to the database. The RunWorkerCompleted method is called when DoWork finishes. RunWorkerCompleted runs on the UI thread, and you can update the view's list with no problems.

// on the UI thread
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += DoWork;
worker.RunWorkerCompleted += RunWorkerCompleted;
worker.RunWorkerAsync("argument");

Events:

static void DoWork(object sender, DoWorkEventArgs e)
{
    e.Result = "4";
}

static void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (e.Error == null)
    {
        string a = (string)e.Result;
        Console.WriteLine(a);
    }
    else
    {
        Console.WriteLine(e.Error.Message);
    }
}
jdot
This seems like it might be a workable solution for me. However, RunWorkerCompleted isn't running on the main thread like it's supposed to. It just keeps on running on the background thread, and I have no idea why.The other potential issue I have is that it's not my UI thread that creates this thread. I think I can work around that though (I did test it by creating the BackgroundWorker straight from the main thread, and it still didn't work).
recoisiche