C# WPF: Linq Fails in BackgroundWorker DoWork Event
I write this because if I ran into this, someone else will. Now first off it wasn’t Linq that failed, but it looked like it and here is my story of failure I found in a WPF background worker threading code which can happen in other areas as well.
The perennial advice to all people in the MSDN forums as well as others is never, never, never write to a GUI control in a thread or the BackgroundWorker’s do work event. All GUI work must be done on a the GUI. I very well knew that…but the reverse (don’t read from a GUI in a different thread) is also true and that bit me. Let me explain.
Anecdotal Evidence
Imagine my surprise when I created a WPF project at a new job and started getting this exception in the DoWork code of my BackgroundWorker when the first use of a Linq query’s delayed execution point was accessed :
The calling thread cannot access this object because a different thread owns it.
The code threw an exception, on line 16 below, where I was loading data from after the Linq call. I began to think, does this have something to do with the Linq DataContext?
void bcLoad_DoWork(object sender, DoWorkEventArgs e) { try { List<string> Results = new List<string>(); DatabaseDataContext dbc = new DatabaseDataContext(); var data = dbc.SystemData .Where(ac => ac.Account_id == tbAccount.Text) .Where(ac => ac.TimeStamp == 0) .Where(ac => ac.Category_id == tbCategory.Text) .Where(ac => (int)ac.Category_seq_nbr == int.Parse(tbSequenceNumber.Text)) .Select(ac => ac.Unit_Code); Results.Add("Unit Code: " + data.First()); e.Result = Results; } catch (Exception ex) { lbxData.Dispatcher.BeginInvoke( new Action(() => lbxData.Items.Add( "Exception Caught: " + ex.Message ))); } }
No the problem was within the setup of the Lambdas for the Linq query. All the highlighted lines above is where the actual problem originates and not on the final highlighted line.
The problem was that I was accessing Gui controls data, and not changing; that was the nuance. For in my mind that was ok, it was a passive read action and not a direct writing one. Obviously not.
Note: If you have come to this blog experiencing this problem but for the writing of items to a control, one method to solve it is to use the Dispatcher off the control on any thread not just BackgroundWorker. That code is shown in my exception catch blog above. That line is perfectly fine to do and is not another issue. The lbxData is a Listbox on the main Xaml and because of the immediacy of the exception, I write
Resolution
Since I was already using the plumbing of the DoWorkEventArgs, it seemed a natural choice to pass in the data using that object. I changed the call to pass in a Dictionary of values to extract the data as such:
bcLoad.RunWorkerAsync(new Dictionary<string, string>() { { "AccountID", tbAccount.Text }, { "CategoryID", tbCategory.Text }, { "SequenceNumber", tbSequenceNumber.Text } });
Then to consume the Dictionary as such:
void bcLoad_DoWork(object sender, DoWorkEventArgs e) { try { List<string> Results = new List<string>(); Dictionary<string, string> UserInputs = e.Argument as Dictionary<string, string>; if (UserInputs != null) { DatabaseContext dbc = new DatabaseContext(); var data = dbc.SystemData .Where(ac => ac.Account_ID == UserInputs["AccountID"]) .Where(ac => ac.TimeStamp == 0) .Where(ac => ac.Category_id == UserInputs["CategoryID"]) .Where(ac => (int)ac.Category_seq_nbr == int.Parse(UserInputs["SequenceNumber"])) .Select(ac => acc.UnitCode); Results.Add("Unit Code: " + data.First()); e.Result = Results; // Pass the results to the completed events to process them accordingly. } } catch (Exception ex) { lbxData.Dispatcher.BeginInvoke( new Action(() => lbxData.Items.Add( "Exception Caught: " + ex.Message ))); } }
I simply convert the object property of Argument to a Dictionary, as highlighted, and go do the work. One doesn’t have to use a Dictionary. One can pass in any object off of the Argument property. Hope This Helps
I stopped using BgWorker for doing async work and moved to Reactive Extensions (Rx).
You should check it out – it allows you to write beautiful declarative code for handling async data (such as events).
Highly recommended!
Excellent suggestion but at the time I was concerned with (not shown in this code) updating the GUI with a progress and being able to cancel other operations which is baked into the BackgroundWorker. :-)
Thank you, this solved my problem!
Thank you very much, this solved my problem.
Nice one ;) was faced with the same problem – thanks a bunch!!!
/Peter