C Walker
C Walker

Reputation: 803

Cross-thread operation exception when worker thread adds to BindingList

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.

Upvotes: 7

Views: 5479

Answers (4)

bporter
bporter

Reputation: 4537

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, not Post.
    mContext.Send(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)
{
    var worker = new MyBackgroundWorkerClass(SynchronizationContext.Current);
}

Upvotes: 1

jdot
jdot

Reputation: 811

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);
    }
}

Upvotes: 0

Marc Gravell
Marc Gravell

Reputation: 1062650

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.

Upvotes: 1

ChrisF
ChrisF

Reputation: 137128

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.

Upvotes: 0

Related Questions