I have a code like the following (I have stripped some code for readability)
private void RicercaArticoloStripped(object sender, DoWorkEventArgs e)
{
try
{
using (var context = new ControlloSchedeLocalEntities())
{
var prodotti = context.VProdotti.Where(i => i.WACMAT == textBoxCodiceArticolo.Text);
if (prodotti.Count() > 0)
{
this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(delegate()
{
textBlockDescrizioneArticolo.Text = prodotti.FirstOrDefault().WADESC;
}));
}
}
}
catch (Exception ex)
{
MessageBox.Show("Error:\n\n" + ex.Message + "\r\nStack: " + ex.ToString());
}
}
private void textBoxCodiceArticolo_KeyUpStripped(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.DoWork += new DoWorkEventHandler(RicercaArticoloStripped);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RicercaArticoloWorkerCompleted);
worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
object[] parameters = new object[] { textBoxCodiceArticolo.Text, textBlockDescrizioneArticolo.Text };
worker.RunWorkerAsync(parameters);
}
}
So, as for my understanding of BackgroundWorker, when I instantiate the:
var prodotti = context.VProdotti.Where(i => i.WACMAT == textBoxCodiceArticolo.Text);
I am doing it FROM the working thread, not the UI thread.
But I get an exception on the line immediately below, when I try to access the value:
if (prodotti.Count() > 0)
I get the (in)famous error:
"The calling thread cannot access this object because a different thread owns it"
Why?
As you already said you must use Dispatcher.BeginInvoke/Invoke to perform operations from the control's owner thread or you will get "The calling thread cannot access this object because a different thread owns it" exception. Thats why you got this exception;
And here is why you got this exception on the line below (when prodotti.Count() was called):
When you create prodotti variable it's just a IEnumerable<T> object. So he actually calculates only when you call for prodotti.Count() method and thats why you got exception on this line.
IEnumerable actually is generator, that means that he will produce new set of objects every time he used.
To test this you can calculate prodotti as shown bellow:
var prodotti = context.VProdotti.Where(i => i.WACMAT == textBoxCodiceArticolo.Text).ToList();
In this case you will get exception immediately because .ToList() forces all calculations.
Check this article for generators and enumerators: http://www.codeproject.com/Articles/155462/IEnumerable-Lazy-and-Dangerous
Updated:
really, when you use IEnumerable with reference types you can get the same objects as previously. Read this answer for more: https://stackoverflow.com/a/14361094/1467309
The property Text of TextBox gets the value of the DependencyProperty Text, this can only be done from the UI-thread. You are accessing textBoxCodiceArticolo.Text from your Worker thread.
Related
I need to perform 2 tasks in parallel. One will load data in the GUI, till then I want to run a progress bar continuously in front of user. I tried BackgroundWorker but it is giving me some Thread synchronization error. Can somebody suggest me any other best way of doing same.
Code:
backgroundWorker1 initialization:
backgroundWorker1 = new BackgroundWorker();
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
backgroundWorker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker1_RunWorkerCompleted);
if (backgroundWorker1.IsBusy != true)
{
backgroundWorker1.RunWorkerAsync();
}
error coming on following line:
XmlDocumentHierarchy _remoteObj = new XmlDocumentHierarchy(comboBox2.Text, "username", "password");
is:
"Cross-thread operation not valid: Control 'comboBox2' accessed from a thread other than the thread it was created on."
You are trying access comboBox2.Text in thread other than GUI thread (background worker thread). If you using only one property in background worker thread, than you can pass `comboBox2.Text' to background worker method:
if (backgroundWorker1.IsBusy != true)
{
backgroundWorker1.RunWorkerAsync(comboBox2.Text);
}
In backgroundWorker1_DoWork procedure you can read property in following way:
void backgroundWorker1_DoWork(Object sender, DoWorkEventArgs e)
{
String comboBoxText = (String)e.Argument;
XmlDocumentHierarchy _remoteObj = new XmlDocumentHierarchy(comboBoxText, "username", "password");
}
If you accessing more than one property from GUI controls you can create simple class to pass all necessary data to your background worker method.
If you need to access GUI thread from BackgroundWorker thread, you can easily invoke your methods in the GUI thread like this:
public Form1()
{
InitializeComponent();
Thread thr = new Thread(new ThreadStart(BackGroundThread));
thr.Start();
}
void BackGroundThread()
{
for (int i = 0; i < 100; i++)
{
// The line below will be run in the GUI thread with no synchronization issues
BeginInvoke((Action)delegate { this.Text = "Processed " + i.ToString() + "%"; });
Thread.Sleep(200);
}
}
I have the following set up:
BackgroundWorker backgroundInstancesWorker = new BackgroundWorker();
backgroundInstancesWorker.DoWork += new DoWorkEventHandler(EnumerateInstances);
backgroundInstancesWorker.WorkerReportsProgress = false;
backgroundInstancesWorker.WorkerSupportsCancellation = false;
backgroundInstancesWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundInstancesWorker_RunWorkerCompleted);
// Create temporary tuple to hold the argument information
// X is type IEnumerable<Foo>, Y is type Bar
object arguments = new object[2] { X, Y };
backgroundInstancesWorker.RunWorkerAsync(arguments);
Thread worker function:
private static void EnumerateInstances(object sender, DoWorkEventArgs e)
{
object[] arguments = e.Argument as object[];
var queryCounterSets = arguments[0] as IEnumerable<Foo>;
var sourceItem = arguments[1] as Bar;
e.Result = sourceItem;
}
Finally completed function:
private static void backgroundInstancesWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Result != null && e.Result is Bar)
{
// Do stuff
}
}
However, in the RunWorkerCompleted function, when I try to access e.Result object, it gives me TargetInvocationException and saying that the calling thread does not have access to the object because another thread owns it. Anyone has any insights as to why this is a problem? I simply want to pass the Bar object into RunWorkerCompleted once the background thread finishes.
Thanks.
Your RunWorkerCompleted event handler should always check the AsyncCompletedEventArgs.Error and AsyncCompletedEventArgs.Cancelled properties before accessing the RunWorkerCompletedEventArgs.Result property. If an exception was raised or if the operation was canceled, accessing the RunWorkerCompletedEventArgs.Result property raises an exception.
The error probably occurred in your background function. See this question for some information on handling cross-thread access of thread-affine objects.
I have a query for getting a list of prescriptions as below:
var PRSCRPTSQuery = GV.dbContext.Load(GV.dbContext.GetPRSCRPTQuery(GV.curCustomer.CustCode,
oOrdritemEdited.ProdCode, oOrdritemEdited.MedCode));
PRSCRPTSQuery.Completed += new EventHandler(PRSCRPTSQuery_Completed);
In the query completed event, I have the following code :
void PRSCRPTSQuery_Completed(object sender, EventArgs e)
{
lstPRSCRPT = GV.dbContext.PRSCRPTs.Where(p=>p.Status =="Activated").ToList();
if (lstPRSCRPT.Count > 0)
{
foreach (var rec in lstPRSCRPT)
{
var OrderItemQuery = GV.dbContext.Load(GV.dbContext.GetOrdritemsQuery(rec.PresNo));
OrderItemQuery.Completed += new EventHandler(OrderItemQuery_Completed);
}
}
}
The list lstPRSCRPT can contain more than one record. I presume, the foreach loop will advance to the next item in the loop without waiting for the OrderItemQuery_Completed event which is below:
void OrderItemQuery_Completed(object sender, EventArgs e)
{
lstOrderItem = GV.dbContext.OrderItems.ToList();
if (lstOrderItem.Count > 0)
{
foreach (var OrdrItemRec in lstOrderItem)
{
TotTonnes = (double)(TotTonnes + OrdrItemRec.Quantity);
}
}
}
Is there any work around for this situation? I am new to the asynchronous type of programming in SL
I see where your coming from and when i first started Silverlight programming i gripped to my preconceptions of synchronous execution so i know what i have when ive finished calling a query and also i know exactly where it's errored.
Silverlight however takes this concept and tries to rip it from you yelling "This way is better trust me!" and for the purpose it serves of enriching client side interactivity it certainly succeeds. It just takes time. You just need to learn more about the style of how to link it all together.
The link previously shown by Faster Solutions shows where C# is going in terms of asynchronous coding but it pays to know what its actually accomplishing for you. Some of which you've already grasped in the code you've linked in the question.
When i've faced the same situation you have where you have back to back async callbacks is to raise an event when i've finished doing what i'm doing. For example:
public event EventHandler<EventArgs> LoadComplete;
public int QueryCount {get;set;}
public int QuerysCompleted {get;set;}
public void GetItems()
{
var PRSCRPTSQuery = GV.dbContext.Load(GV.dbContext.GetPRSCRPTQuery
(GV.curCustomer.CustCode, oOrdritemEdited.ProdCode, oOrdritemEdited.MedCode));
PRSCRPTSQuery.Completed += new EventHandler(PRSCRPTSQuery_Completed);
LoadComplete += loader_LoadComplete;
}
void PRSCRPTSQuery_Completed(object sender, EventArgs e)
{
lstPRSCRPT = GV.dbContext.PRSCRPTs.Where(p=>p.Status =="Activated").ToList();
if (lstPRSCRPT.Count > 0)
{
QueryCount = lstPRSCRPT.Count;
foreach (var rec in lstPRSCRPT)
{
var OrderItemQuery = GV.dbContext.Load(GV.dbContext.GetOrdritemsQuery(rec.PresNo));
OrderItemQuery.Completed += new EventHandler(OrderItemQuery_Completed);
}
}
}
void OrderItemQuery_Completed(object sender, EventArgs e)
{
QueryCompleted++;
lstOrderItem = GV.dbContext.OrderItems.ToList();
if (lstOrderItem.Count > 0)
{
foreach (var OrdrItemRec in lstOrderItem)
{
TotTonnes = (double)(TotTonnes + OrdrItemRec.Quantity);
}
}
if(QueryCompleted == QueryCount)
{
RaiseLoadComplete();
}
}
public void RaiseLoadComplete()
{
if(LoadComplete != null)
{
LoadComplete(this, new EventArgs());
}
}
void loader_LoadComplete(object sender, EventArgs e)
{
//Code to execute here
}
I attach an event when starting the first query of what code to execute when i'm done. In the first query call back i initialise a count of how many responses i am expecting. Then in the second query callback i increment until i get the right amount and call the event to say im done.
The only caution with this approach is if one of the queries error, The final code will never get executed.
You might find that the VS Async CTP of interest. It introduces the new "async" keyword for handling asyncronous events. He's a blog explaining it: VS Async
In my Windows I have a TextBox which I like to update (text property) from another thread. When doing so, I get the InvalidOperationException (see title). I have found different links in google explaining this, but I still can't seem to make it work.
What I've tried is this:
Window1 code:
private static Window1 _myWindow;
private MessageQueueTemplate _messageQueueTemplate;
private const string LocalTemplateName = "LocalExamSessionAccessCodeMessageQueueTemplate";
private const string RemoteTemplateName = "RemoteExamSessionAccessCodeMessageQueueTemplate";
...
public Window1()
{
InitializeComponent();
_myWindow = this;
}
public static Window1 MyWindow
{
get
{
return _myWindow;
}
}
public void LogText(string text)
{
informationTextBox.Text += text + Environment.NewLine;
}
...
In another class (actually a spring.NET Listener adapter, listening to a certain queue, started in another thread).
var thread = new Thread(
new ThreadStart(
delegate()
{
Window1.MyWindow.Dispatcher.Invoke(
System.Windows.Threading.DispatcherPriority.Normal,
new Action(
delegate()
{
Window1.MyWindow.LogText(text);
}
));
}
));
It doesn't throw an error, but the text in the LogText method in Window1 isn't triggered, so the text isn't updated.
So basically, I want to update this TextBox component from another class running in another thread.
Window1.MyWindow.informationTextBox.Dispatcher.Invoke(
DispatcherPriority.Normal,
new Action(() => Window1.MyWindow.informationTextBox.Text += value));
Well, using Dispatcher.Invoke or BeginInvoke is definitely the way to go, but you haven't really shown much code other than creating a thread - for example, you haven't started the thread in your second code block.
If you put the Dispatcher.Invoke code in the place where previously you were getting an InvalidOperationException, it should be fine.
For WPF, I find this construct:
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += ( s, e ) =>
{
};
bw.RunWorkerCompleted += ( s, e ) =>
{
};
bw.RunWorkerAsync();
to be the most useful. The RunWorkerCompleted block will typically update an ObservableCollection or fire off a RaisePropertyChangedEvent.
Firstly, I know I should be using proper Threading techniques (Threadpool, BeginInvoke, etc.) to accomplish this, but thats a bit over my head currently and will call for some time to read over material and understand it (if you have any URL references for my scenario, please feel free to post it).
In the interim I am using the backgroundWorker to pull a very large resultset and populate a DatagridView with it. I successfully create a SortableBindingList<TEntities> in my DoWork event and pass that out in the result. And in the RunWorkerCompleted event, I cast and bind that SortableBindingList<TEntities> to my Grid. My 2 main areas of concern are as follows:
1) Access to private variables.
I want to pass one of two parameters List<long> into my DoWork event, but run a different query depending on which list was passed to it. I can get around this by declaring a class-level private boolean variable that acts a flag of sorts. This seems silly to ask, but in my DoWork, am I allowed to access that private variable and route the query accordingly? (I've tested this and it does work, without any errors popping up)
private bool SearchEngaged = false;
private void bgw_DoWork(object sender, DoWorkEventArgs e) {
BackgroundWorker worker = sender as BackgroundWorker;
e.Result = GetTasks((List<long>)e.Argument, worker, e);
}
SortableBindingList<Task> GetTasks(List<long> argsList, BackgroundWorker worker, DoWorkEventArgs e) {
SortableBindingList<Task> sbl = null;
if (worker.CancellationPending) {
e.Cancel = true;
}
else {
if (SearchEngaged) {
sbl = DU.GetTasksByKeys(argsList);
}
else {
sbl = DU.GetTasksByDivision(argsList);
}
}
return sbl;
}
2) UI Thread freezes on beginning of RunWorkerCompleted.
Ok, I know that my UI is responsive during the DoWork event, 'cos it takes +/- 2seconds to run and return my SortableBindingList<Task> if I don't bind the List to the Grid, but merely populate it. However my UI freezes when I bind that to the Grid, which I am doing in the RunWorkerCompleted event. Keep in mind that my Grid has 4 image columns which I handle in CellFormatting. This process takes an additional 8 seconds to accomplish, during which, my UI is completely non-interactive. Im aware of the cross-thread implications of doing so, but is there any way I can accomplish the Grid population and formatting either in the background or without causing my UI to freeze? RunWorkeCompleted looks like so:
private void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
if (e.Cancelled) {
lblStatus.Text = "Operation was cancelled";
}
else if (e.Error != null) {
lblStatus.Text = string.Format("Error: {0}", e.Error.Message);
}
else {
SortableBindingList<Task> sblResult = (SortableBindingList<Task>)e.Result;
dgv.DataSource = sblResult;
dgv.Enabled = true;
TimeSpan Duration = DateTime.Now.TimeOfDay - DurationStart;
lblStatus.Text = string.Format("Displaying {0} {1}", sblResult.Count, "Tasks");
lblDuration.Visible = true;
lblDuration.Text = string.Format("(data retrieved in {0} seconds)", Math.Round(Duration.TotalSeconds, 2));
cmdAsyncCancel.Visible = false;
tmrProgressUpdate.Stop();
tmrProgressUpdate.Enabled = false;
pbStatus.Visible = false;
}
}
Sorry for the lengthy query, but I will truly appreciate your responses! thank you!
Your code appears to be doing exactly the right thing.
As for the 8 seconds that it takes for the UI thread to update the screen, there's not much you can do about that. See my answer to this question.
To optimise the UI part, you could try calling SuspendLayout and ResumeLayout on the grid or its containing panel.
You could also look at trying to reduce the amount of processing that is done during the data binding. For example:
Calculations done in the grid could be moved into the data model (thereby doing them in the worker thread).
If the grid auto-calculates its columns based on the data model, then try hard-coding them instead.
EDIT: Page the data in the Business Layer and make the grid only show a small number of rows at a time.
I think the easiest solution for your problem is setting the datasource of your grid in DoWork instead of RunWorkerCompleted using Dispatcher.BeginInvoke which you have mentioned yourself. Something like this:
private bool SearchEngaged = false;
private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
SortableBindingList<Task> sblResult = GetTasks((List<long>)e.Argument, worker, e);
BeginInvoke((Action<object>)(o => dataGridView1.DataSource = o), sblResult);
}
private void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled) {
lblStatus.Text = "Operation was cancelled";
}
else if (e.Error != null) {
lblStatus.Text = string.Format("Error: {0}", e.Error.Message);
}
else
{
dgv.Enabled = true;
TimeSpan Duration = DateTime.Now.TimeOfDay - DurationStart;
lblStatus.Text = string.Format("Displaying {0} {1}", sblResult.Count, "Tasks");
lblDuration.Visible = true;
lblDuration.Text = string.Format("(data retrieved in {0} seconds)", Math.Round(Duration.TotalSeconds, 2));
cmdAsyncCancel.Visible = false;
tmrProgressUpdate.Stop();
tmrProgressUpdate.Enabled = false;
pbStatus.Visible = false;
}
}
As far as the private variable issue is concerned, I don't think it will be of any problem in your case. In case you are changing it using some UI event, just mark the private field as volatile. The documentation of the volatile keyword can be found here:
http://msdn.microsoft.com/en-us/library/x13ttww7.aspx