It's not the first time I come across delegates and I am as confused as I were the last time and the time before that. So once and for all I want to get the delgate-confusion cleared up.
My problem is as followed
Having a Graphical User Interface which only displays a ListView with some boud items, I want to load the data from a Data Connection which takes some time, to increase the comfort of using the application I have instancieted a BackgroundWorker and in the doWork-method I want to fetch the data and display it.
This is how I want it
Create BackgroundWorker and appoint doWork_fetchData() method to the doWork event
Call the Async method of my Worker instance
Have the ListView updated without the User Interface beeing "frozen" during download of data.
Now this is Cross-Thread-Invoking and I wanted to solve this with Delegates which brings me here. Following this tutorial, I got a working Delegate, However it did not solve the problem, inside my delegate I cannot change my ListView, it still says it is on another thread.
I want to find an Easy explenation on delegates and how to use them to solve my problem. Also, should I think or design my software different?
Normally BackgroundWorker communicates with the UI thread using ReportProgress. You would hook up a delegate to receive those progress events before launching the background worker, and then the progress would be reported on the UI thread, where you're safe to change your ListView.
The other alternative in Windows Forms is to call Control.Invoke or Control.BeginInvoke, passing in a delegate which will update the UI. That delegate will be executed on the UI thread. For an example of this, see my threading tutorial or Joe Albahari's.
The equivalent of this in WPF is the Dispatcher - again, Invoke and BeginInvoke. You can access the dispatcher for a control with the Dispatcher property.
You can't change a ui control from a different thread directly, you need to check the Control.InvokeRequired property before you make a change.
See this example on msdn
Checkout this code, it does what you need:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void btnFill_Click(object sender, EventArgs e)
{
backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
backgroundWorker1.RunWorkerAsync();
}
private delegate void AddItemToListViewDelegate(ListView view, ListViewItem item);
private void AddItemToListView(ListView view, ListViewItem item)
{
if (InvokeRequired)
{
Invoke(new AddItemToListViewDelegate(AddItemToListView), new object[] { view, item });
return;
}
view.Items.Add(item);
}
private delegate void ClearListViewItemsDelegate(ListView view);
private void ClearListView(ListView view)
{
if (InvokeRequired)
{
Invoke(new ClearListViewItemsDelegate(ClearListView), new object[] { view });
return;
}
view.Items.Clear();
}
void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 100; i++)
{
if (i == 0)
ClearListView(listView1);
var item = new ListViewItem();
item.Name = i.ToString();
item.Text = item.Name;
AddItemToListView(listView1, item);
}
}
}
And for WPF something similar is required. Note this is not working code. As I don't use WPF I can't vouch that this is solid code, but it should give you an idea. You may need to create an type derived from EventArgs to encapsulate your listview and listviewitems.
If I get time, I'll edit this post so that it works, but that will have to wait until this evening!
using System.Windows.Threading;
...
if (listView1.Dispatcher.Thread != Thread.CurrentThread)
{
listView1.Dispatcher.BeginInvoke(
DispatcherPriority.Normal,
new EventHandler<ListViewAddEventArgs>(AddItemToListView), sender, new object[] { e } );
return;
}
listView1.Items.Add(e.File);
Related
I am stuck on an issue where I am using Backgroundworker to show the progress of my work in a progress bar. Code used for backgroundworker:-
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(200);
for (int i = 0; i <= 100; i++)
{
Delegate del= new DELEGATE(simulateHeavyWork);
this.Invoke(del);
backgroundWorker1.ReportProgress(i);
if (backgroundWorker1.CancellationPending)
{
e.Cancel = true;
backgroundWorker1.ReportProgress(0);
return;
}
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
percentLabel.Text = e.ProgressPercentage.ToString() + "%";
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
MessageBox.Show("Cancelled");
}
else
{
MessageBox.Show("Completed");
}
}
I have created a delegate on the code:-
public partial class Form1 : Form
{
private delegate void DELEGATE();
public Form1()
{
InitializeComponent();
}
private void simulateHeavyWork()
{
Thread.Sleep(100);
...lines of code to perform some search logs operation..
....
}
}
The functionality I want to achieve is that progress bar should report the progress of my function simulateHeavyWork() which is actually using UI thread as it needs to take input from my form controls and update it as well.
Now the problem which is happening is that code is actually calling simulateHeavyWork() and gives the output that is updating ui controls and work is done. (Note: I have used delegate here to avoid error cross controls running on ui thread as my function needs to use UI controls.)
Once that is done, it starts updating progress bar and which is wrong and looks like it calls simulateHeavyWork again and again with the gap of sleep(100).
user3222101, as Andy stated before, you are running simulateHeavyWork() continuously. Moreover, by calling Invoke you are running this method in the UI thread which cause an extra sleep in the UI thread. Basically Invoke uses the message loop (pump) of the Control you use it on (Form1 in that case) and put your delegate to the queue of the UI thread in order to execute. This is not a good practice I think, due to the Sleep() call and time consuming log operations in your simulateHeavyWork() method.
I hope, understand you problem clearly.What I suggest is separation of the time consuming log operations from UI thread. Do not spend the valuable time of UI thread with slow and boring I/O operations. Get the values from the controls (using Invoke in the BackgroundWorker as I will explain below), do whatever you want in BackgroundWorker and update your GUI (again using Invoke) without touching the UI thread for this kind of heavy tasks.
As Andy suggested, you can pass data via the parameter of RunWorkerAsync and you should create a class which can store any data you need (because it accepts only one parameter). However, you can get the values from your Form whenever you need from another thread by using Invoke. Invoke
method also returns the value from your delegate (please see the example at the link below) and this gives you a chance to get the values of your controls on the form. Create a delegate which returns an object of type class that you crated for RunWorkerAsync and use this values in the BackgroundWorker thread. Please, have a look at the example in here.
public static string GetTextThreadSafe(this TextBox box)
{
return GetTextBoxText(box);
}
Also, example uses Func<...> in order to return value.
By this way you can sleep (in BackgroundWorker thread) for a while then get the values from your controls (current values) and do whatever you want (again in BackgroundWorker thread). I think, this improves your code.
From your question: "which is wrong and looks like it calls simulateHeavyWork again and again with the gap of sleep(100)."
Of course it calls. Just look at your code:
for (int i = 0; i <= 100; i++)
{
Delegate del= new DELEGATE(simulateHeavyWork);
this.Invoke(del);
So you are calling simulateHeavyWork 100 times here. And since you've typed Thread.Sleep(100); in the body of simulateHeavyWork - gap between calls is about Sleep(100)
I'm writing a program that handles DBs and writes any changes into ListView for user to proccess them. After that It is suposed to write all changes back into DB but I can't figure out how to reach ListView.ListViewItemCollection from BGW. I've tried to use Control.Invoke but I'm affraid I'm not yet skilled enough to make it work.
The error I'm getting says I can't access that control from thread that it was not created on
private delegate void BGOUdate(ListView.ListViewItemCollection lvic);
BGOU = new BGOUdate(ApplyChanges);
bgw1.RunWorkerAsync(lvProducts.Items);
private void bgwSearcher_DoWork(object sender, DoWorkEventArgs e)
{
BGOU(e.Argument as ListView.ListViewItemCollection);
}
private void ApplyChanges(ListView.ListViewItemCollection lvic)
{
...
foreach (ListViewItem item in lvic)
{
...
}
...
}
Control.Invoke is the right way to walk. But you need to pass the control to the background worker, not just its ItemCollection:
private void ApplyChanges(ListView lv)
{
lv.Invoke((Action)delegate
{
foreach (ListViewItem item in lv.Items)
{
...
}
});
...
}
This way, all changes to the ListViewItems will be made in the thread that created the controls.
Action is a predefined void delegate that takes no arguments. The keyword delegate marks the following block as an anonymous function and returns a delegate to this, which is then cast to an Action that can be invoked on the control. The call to Invoke causes the passed delegate to be executed on the thread that is associated with the control's window handle, which is almost everytime the creator thread.
Maybe, you should consider DataBinding as an option to keep GUI elements in sync with your data.
In my application I am using a timer to check for updates in an RSS feed, if new items are found I pop up a custom dialog to inform the user. When I run the check manually everything works great, but when the automatic check runs in the timers Elapsed event the custom dialog is not displayed.
First of all is this a thread issue? (I am assuming it is because both the manual and automatic check use the same code).
When I run the automatic check, do I have to invoke the method that runs the check from the Timers Elapsed event handler?
Is there something I need to do in my custom dialog class?
Edit:
this is a winforms application.
Here is an example of what the code is like. (Please don't point out syntax errors in this code example, this is just a simple example not real code).
public class MainForm : System.Windows.Forms.Form
{
//This is the object that does most of the work.
ObjectThatDoesWork MyObjectThatDoesWork = new ObjectThatDoesWork();
MyObjectThatDoesWork.NewItemsFound += new NewItemsFoundEventHandler(Found_New_Items);
private void Found_New_Items(object sender, System.EventArgs e)
{
//Display custom dialog to alert user.
}
//Method that doesn't really exist in my class,
// but shows that the main form can call Update for a manual check.
private void Button_Click(object sender, System.EventArgs e)
{
MyObjectThatDoesWork.Update();
}
//The rest of MainForm with boring main form stuff
}
public class ObjectThatDoesWork
{
System.Timers.Timer timer;
public ObjectThatDoesWork()
{
timer = new System.Timers.Timer();
timer.Interval = 600000;
timer.AutoReset = true;
timer.Elapsed += new new System.Timers.ElapsedEventHandler(TimeToWork);
timer.Start();
}
private void TimeToWork(object sender, System.Timers.ElapsedEventArgs e)
{
Update();
}
public void Update()
{
//Check for updates and raise an event if new items are found.
//The event is consumed by the main form.
OnNewItemsFound(this);
}
public delgate void NewItemsFoundEventHandler(object sender, System.EventArgs e);
public event NewItemsFoundEventHandler NewItemsFound;
protected void OnNewItemsFound(object sender)
{
if(NewItemsFound != null)
{
NewItemsFound(sender, new System.EventArgs());
}
}
}
After reading some of the comments and answers, I think my problem is that I am using a System.Timers.Timer not a System.Windows.Forms.Timer.
EDIT:
After changing to a Forms.Timer initial testing looks good (but no new items exist yet so have not seen the custom dialog). I added a bit of code to output the thread ID to a file when the update method is called. Using the Timers.Timer the thread ID was not the GUI thread, but using the Forms.Timer the thread ID is the same as the GUI.
Which timer are you using? System.Windows.Forms.Timer automatically fires the event on the UI thread. If you are using other one you will need to use Control.Invoke to call the method on UI thread.
You should use Forms.Timer here, or if you use other kind of timers, serialize calls to UI with .Invoke()
Is your application a WPF-Application? If so, you must delegate the work from your background-thread to the Dispatcher associated with the UI thread.
Post some code, so you can get better help and have a look at the Dispatcher class http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcher.invoke.aspx
private static System.Threading.SynchronizationContext _UI_Context;
//call this function once from the UI thread
internal static void init_CallOnUIThread()
{
_UI_Context = System.Threading.SynchronizationContext.Current;
}
public static void CallOnUIThread(Action action, bool asynchronous = false)
{
if (!asynchronous)
_UI_Context.Send((o) =>
{
action();
}, null);
else
_UI_Context.Post((o) =>
{
action();
}, null);
}
I hava a Background Worker and a DataGrid in my c# Application. In do work of my Backgroundworker which will call an Api in my dlls which will enter some Data in a SQLite Database. After the Completion of my Api call I report a progress and In progress event of my Backgroundworker I get the contents from Db and assign it as a DataSource to my grid. I call same API in same backgroundworker. In the middle of processing my application crashes. But If I dont assign the dataSource in ProgressChanged my application doesnt crashes.
I am assuming you must be accessing UI object using Invoke method.
If not try using following approach (Executes the specified delegate, on the thread that owns the control's underlying window handle, with the specified list of arguments.):
//In Form.Designer.cs
Label myLabel = new Label();
//In code behind under Background worker method
LabelVlaueSetter SetLabelTextDel = SetLabelText;
if (myLabel .InvokeRequired)
{
myLabel.Invoke(SetLabelTextDel, "Some Value");
}
private delegate void LabelVlaueSetter(string value);
//Set method invoked by background thread
private void SetLabelText(string value)
{
myLabel.Text = value;
}
As Johnathan Allen mentions, it should not matter. Unless something else is going on. I have had two cases where I could not interact with certain controls in the events generated by the BackgroundWorker. The only thing that worked was using the Invoke method.
Try assigning the DataSource on the same thread that created the DataGridView control. You do this through the control's Invoke method. Use the following code. (I have not tested, but this is the standard pattern.)
If this does not work then try Jonathan Allen's suggestion.
Actually, do whichever suggestion is easiest to try first.
private delegate void SetDataSourceDelegate(object value);
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) {
DataTable oData = null; //'assign data source
if (dataGridView1.InvokeRequired) {
dataGridView1.Invoke(new SetDataSourceDelegate(SetDataSource), new Object[] {oData});
}else{
SetDataSource(oData);
}
}
private void SetDataSource(object value) {
dataGridView1.DataSource = value;
}
It shouldn't matter, but why are you using ProgressChanged instead of RunWorkerCompleted?
Also, try doing everything on the GUI thread without the BackgroundWorker. That will let you know if the problem is in your code or how your code interacts with the GUI.
To play a bit with threading, delegates and backgroundworkers, I'm putting together a few small applications, I'm having a bit of trouble with one of them.
I've a Windows form, with a textbox, a button and a richttext.
When I press the button, the text in the textbox is used as a paramter to instantiate a class, like this:
public partial class Form1 : Form
{
private BackgroundWorker backgroundWorker;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += new DoWorkEventHandler(worker_DoWork);
backgroundWorker.RunWorkerAsync();
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
new Thread((ThreadStart)delegate()
{
this.BeginInvoke((ThreadStart)delegate()
{
foreach (string line in textBox1.Lines)
{
Dig digger = new Dig(line, textBox1.Text);
digger.DomainChecked += new Dig.DomainCheckedHandler(OnUpdateTicker);
string response = digger.GetAllInfo();
richTextBox1.AppendText(response);
Application.DoEvents();
}
});
}).Start();
}
void OnUpdateTicker(string msg)
{
new Thread((ThreadStart)delegate()
{
this.BeginInvoke((ThreadStart)delegate()
{
label4.Text = msg;
Application.DoEvents();
});
}).Start();
}
}
When debugging I run into a 'textBox1.Lines' threw an exception of type 'Microsoft.VisualStudio.Debugger.Runtime.CrossThreadMessagingException'
Any tips on how to solve this problem?
First, there is no need to create new threads inside DoWork; the whole idea with the BackgroundWorker is that DoWork is executed on a separate thread. Second, since DoWork is executed on a separate thread and UI controls can be modified only on the UI thread, you need to invoke those updates correctly. So, a rewritten version of worker_DoWork could look like this:
void worker_DoWork(object sender, DoWorkEventArgs e)
{
foreach (string line in textBox1.Lines)
{
Dig digger = new Dig(line, textBox1.Text);
digger.DomainChecked += new Dig.DomainCheckedHandler(OnUpdateTicker);
string response = digger.GetAllInfo();
richTextBox1.Invoke((Action) delegate { richTextBox1.AppendText(response); });
}
}
Note how the code does not explicitly spawn any new threads, and also how the AppendText method call is done through a Control.Invoke call, forcing it to execute on the UI thread.
The main reason is that the textbox is not owned by the background thread.
Your UI thread owns all the UI objects, and you're spinning up a background thread when a button is pressed. That background thread should not have access to any UI objects.
If you want the value of the textbox to be used, you'll need to pass it to your background thread another way.
Have a look here for an explanation (and solution).
You can only update controls on the main thread from the main thread itself, unless you explicitly tell your program that it's ok to do, by using the .Invoke method of the control.
From: http://www.albahari.com/threading/part3.aspx
Control.Invoke
In a multi-threaded Windows Forms application, it's illegal to call a method or property on a control from any thread other than the one that created it. All cross-thread calls must be explicitly marshalled to the thread that created the control (usually the main thread), using the Control.Invoke or Control.BeginInvoke method. One cannot rely on automatic marshalling because it takes place too late – only when execution gets well into unmanaged code, by which time plenty of internal .NET code may already have run on the "wrong" thread – code which is not thread-safe.