I have a Winform with 4 PictureBox controls, each control will contain a diferent image. The process is:
An event x is raised, the eventargs from this event, contain the filenames of each image (4), an so on (file exists etc..). Then, I have to update the UI.
Commonly I use Invoke:
Invoke((ThreadStart)delegate()
{
picBig.Image = new Bitmap(strImageBig);
picLittle1.Image = new Bitmap(saLittle[0]);
picLittle2.Image = new Bitmap(saLittle[1]);
picLittle3.Image = new Bitmap(saLittle[2]);
});
// saLittle[] is a string array, contains, filenames: "image1.jpg"
But when this executes, the form freezes for a little time, about 500ms, I know it's a small interval but it's noticeable, then the app continues normally.
I was trying to find out the reason of that 'UI freeze', then, after research, I found BeginInvoke. Now my code looks like this:
BeginInvoke((MethodInvoker)delegate
{
picBig.Image = new Bitmap(strImageBig);
picLittle1.Image = new Bitmap(saLittle[0]);
picLittle2.Image = new Bitmap(saLittle[1]);
picLittle3.Image = new Bitmap(saLittle[2]);
});
This is a little faster. But the UI is still freezing for 200~300ms.
In the articles I've read, they say that BeginInvoke is a better way than Invoke.
The code is working OK, there is no problem with logic or anything else. I just want to know why this happens. I don't want to leave this doubt unclear. The project is already finished. Hope this be useful for someone else.
Maybe this is not the correct approach. I know there are many ways to update the UI from a background thread, but is there another way to make an update faster?
Or, do you think, the image loading is the reason? Is there another way to do a faster loading of images?
Thanks in advance.
This is because you're actually loading your images from disk on the UI thread along with setting the contents of the control. Calling the Bitmap constructor with a file path will go to the hard drive and load the image into memory.
Invoke and BeginInvoke will run the delegate that you provide on the thread that the control was created on, which is most likely going to be the UI thread.
...but is there another way to make an update faster?
Load the images on your background thread and, when they're actually loaded, invoke and set the images into the controls.
var big = new Bitmap(strImageBig);
var little1 = new Bitmap(saLittle[0]);
var little2 = new Bitmap(saLittle[1]);
var little3 = new Bitmap(saLittle[2]);
Invoke((ThreadStart)delegate()
{
picBig.Image = big;
picLittle1.Image = little1;
picLittle2.Image = little2;
picLittle3.Image = little3;
});
But when this executes, the form freezes for a little time, about 500ms, I know it's a small interval but it's noticeable, then the app continues normally.
Eventually, the UI thread needs to actually update the images. When the images are generated and updated on the UI thread, this will cause a (short) delay.
Related
I'm discovering this and can't get to understand how it works.
I have a progress bar in my main form and a menuStrip with a few maintenance functions on a mysql DB. I'd like to monitor progress for each of them.
So I have a class report and a Progress progress.
My declarations are as follow :
ProgressReportModel report = new ProgressReportModel();
Progress<ProgressReportModel> progress = new Progress<ProgressReportModel>();
progress.ProgressChanged += ReportProgress;
Now for each of the functions, I usually start with :
report.max = count;
report.visibility = true;
report.PercentageComplete = 0;
showtheProgress(progress);
with ShowtheProgress because I didn't find a way to convert from Progress to IProgress:
private void showtheProgress(IProgress<ProgressReportModel> progress)
{
progress.Report(report);
}
My issue is that this works for some functions but not all.
One difference that i can see if that it works with functions which are async but not with functions which are not async. To void flooding with code, just gonna put one function which is not working (ie the progressbar is not even showing up):
private void getMp3Files()
{
Globals.counttot = Directory.GetFiles(Globals.pathdir, "*.mp3", SearchOption.AllDirectories).Length;
report.max = Globals.counttot;
report.visibility = true;
report.PercentageComplete = x;
showtheProgress(progress);
DirectoryCopy(Globals.pathdir);
report.visibility = false;
report.PercentageComplete = 0;
showtheProgress(progress);
}
private void DirectoryCopy(string sourceDirName)
{
DirectoryInfo dir = new DirectoryInfo(sourceDirName);
DirectoryInfo[] dirs = dir.GetDirectories();
FileInfo[] files = dir.GetFiles("*.mp3");
foreach (FileInfo file in files)
{
string temppath = Path.Combine(sourceDirName, file.Name);
Mp3 tmp_mp3 = Globals.getTags(temppath);
Globals.AddRowDs(tmp_mp3, false);
report.PercentageComplete = x; //not part of this code but it's a counter
showtheProgress(progress);
}
foreach (DirectoryInfo subdir in dirs) DirectoryCopy(subdir.FullName);
}
Thanks in advance!
It's not completely clear from the code that you have posted but my guess is that you have this problem because of the way how winforms works with threads and one of the reasons why the async was 'invented'.
A windows form is based on something like a message queue. Everything that you do on that form like moving your mouse, clicking a button, moving/resizing the form, typing and many more is converted to events and placed in this queue. In the background there is something constantly checking this queue for new events and executing them, one of these events is to draw the screen (paint), basically showing your form on the screen. You can only see the changes that are made (like showing a progress bar) during these paint events. If the time between these paint events is too long you see the message "Not responding".
Some of these events are also clicks on a button and these will execute all the code that you made. If the process of this event takes too long, then you will hold up the message queue and create the "Not responding" message. In order to avoid this, it's suggested to make these events as quick as possible, if you want to execute something that takes a long time, make it on another thread (see BackgroundWorker for an example). One downside of this is that it's not easy to communicate with the form (typically checking InvokeRequired and calling Invoke)
Here is where the async/await comes to help you. If you have a button click, and your code encounters an await that is not done yet, then it will add some code that, once the awaited method is done, it will add an event to the message queue to continue from this point in your code and end the event. This means that the rest of the events in the message queue (like showing that progress bar, displaying some text or handling other button clicks) are possible.
Just to be complete, some can suggest that you can sprinkle your code with Application.DoEvents(), these will force the message queue to be processed while you are still inside the method. This is a quick and dirty way that some people take without knowing the full implications of this. Know that it exists but avoid it.
I have a GUI that is for all intents and purposes really basic. A listview, an html form, and that's really it.
I want the user to have the following behavioral ability:
1 - Click a checkbox that says "Real-time". When clicked, a background thread will run once every 10 seconds.
2 - If there is a new file created (this is easy, to observe a new file) I want an alert displayed in my main gui. Where it is displayed for now is arbitrary (in a label, for example).
The main issue is I cannot figure out how to do this in a multi-threaded example. My goal is exactly in line with multithreading: do tasks 1 and 2, without locking task 1. Meaning, while the update check is running, the user can interact with the GUI as if nothing was going on in the background.
If you need more details to better answer this please let me know.
Thanks!
Here are a couple sites I found useful for implementing a background worker when I needed to perform database operations while still allowing the GUI to be responsive:
http://msdn.microsoft.com/en-us/library/zw97wx20.aspx
http://www.codeproject.com/KB/cs/AsynchronousCodeBlocks.aspx
Use events from the thread to tell the UI that something's changed:
// Just detected that that a new file has been created
if (this.FileCreated_Event != null)
{
this.FileCreate_Event(this, new FileEventArgs(newFileName));
}
where FileCreated_Event and FileEventArgs are declared appropriately.
Then in the UI when you receive the event you have the following:
this.fileChecker.FileCreated_Event += this.FileCreated_Event;
and:
private void FileCreated_Event(object sender, TrackStatusEventArgs e)
{
if ((sender as Control).InvokeRequired)
{
(sender as Control).Invoke(action);
}
else
{
action();
}
}
where action is the thing you want to do.
Try this tutorial. At the end I'm sure you'll be able to use threads. You must be careful though, because you'll have to manage those threads which can be a daunting task. I've never met a programmer who liked to debug multiple threads...
I have a c# form, and the initialization time takes a while (its getting information from a server, and populating a TreeView). Right now, the code looks similar to this:
public class myForm : Form
{
InitializeComponent();
List<Location> locations = getServerLocations(); // Server call
foreach( Location loc in locations )
{
List<POI> POIs = loc.getLocationPOIs(); // Server call
foreach( POI poi in POIs )
{
List<POIDetails> = poi.getPOIDetails(); // Server call
....
}
}
}
you get the point I think ... So there is a large tree, and I know I can not make the calls all the way down until the user expands the tree. But the intent is I just want the Form to display, with a 'loading...' or something on a tool strip while all the processing and server gets are happening.
Right now, it seems as if I haven't loaded the application yet because nothing will show to the user until all the calls are complete.
You shouldn't do any long running processing on the UI thread - instead move this to another thread i.e using a BackgroundWorker. You can initially show the "Loading" screen and, once the background worker completes, update your UI with your tree structure.
You should work with multi threading process, so that you can separate the process that takes time from the rest of the process. Here is a blog that may help you. .NET 4.0 and System.Threading.Tasks
Running your initialization on a separate thread is the preferred way. But if you're constrained to run it on the UI thread then try calling Application.DoEvents() right after your call to .Show() or .ShowDialog() of your form.
If the form shows up, it will still be unresponsive to user actions until the initialization is completed. So running the initialization on a separate thread is the better solution.
In my application I display data from a online web service into several UITableViews. I have added several ways for the user to update the data, but the TableView.ReloadData() method does not seem to work.
When the user calls for an update, I get a new set of data from the server, pass it to the UITableViewSource instance that is attached to the UITableViewController and then call the ReloadData() method, which unfortunately does not in fact reload the data. Only after I return to the main screen and then go back to the table view (because it is already created, I just display the instance that already exists) does the new data show up in the tableview.
What am I doing wrong? I tried creating a new instance of the UITableViewSource when updating the data, but that does not help either.
Here is the code for loading data into the tableview (I reuse it for any event that requires data to be loaded into it):
dataControl.GetList(Tables.UPDATES)); //gets data from the server and passes it to the SQL database
Source source = GetSource(theType.Name, theType, groups); //creates a new source loaded with the data
Updates.TableView.Source = source;
Updates.TableView.AllowsSelection = false;
Updates.TableView.ReloadData();
This code is of course executed in a separate thread that invokes on the main thread.
Basically the same code is called when the user asks for an update(an animation is played while the background thread works).
Pavel is correct - try the following to see if it works:
InvokeOnMainThread(delegate{
Updates.TableView.Source = source;
Updates.TableView.AllowsSelection = false;
Updates.TableView.ReloadData();
});
In future, whenever you're dealing with something that will change the UI currently shown, you will need to ensure that it takes place on the main thread (also known as the GUI thread). This InvokeOnMainThread is a method from NSObject so can be called like above in all UIViews / UIViewControllers etc - you can also call it from an entirely C# class using:
MonoTouch.UIKit.UIApplication.SharedApplication.InvokeOnMainThread(delegate{ /*code here*/ });
You say it is done in a different thread, could it be that the worker thread cannot call the UI thread? I.e. you should call the ReloadData via delegate to be sure it gets called in the UI thread and not "only" in the worker thread, as it might interlock and never get actually called (happened to me in a different scenario).
I also ran into this problem and found this question. Using some of the hint here, I finally got it work by reseting the DataSource and call ReloadView().
tableView.DataSource = new MyDataSource(...);
tableView.RelaodData();
From my testing, it doesn't make different if I wrap the ReloadView() within the InvokeOnMainThread or not. Well, that's maybe because I'm not using worker thread.
It is strange that in another case I could refresh the table view by simply calling ReloadData() in ViewDidAppear(). The only difference is that the above case is the refresh within the same view.
You can reload datain viewwillappear() or set load data code in viewwillappear().
For googlers:
I had the same issues. This is what fixed it for me
InvokeOnMainThread(delegate {
myTableView.Source = new TableViewSource();
myTableView.ReloadData();
this.View.SetNeedsDisplay();
});
I have a C# desktop application in which one thread that I create continously gets an image from a source(it's a digital camera actually) and puts it on a panel(panel.Image = img) in the GUI(which must be another thread as it is the code-behind of a control.
The application works but on some machines I get the following error at random time intervals(unpredictable)
************** Exception Text **************
System.InvalidOperationException: The object is currently in use elsewhere.
Then the panel turns into a red cross, red X - i think this is the invalid picture icon that is editable from the properties. The application keeps working but the panel is never updated.
From what I can tell this error comes from the control's onpaint event where I draw something else on the picture.
I tried using a lock there but no luck :(
The way I call the function that puts the image on the panel is as follows:
if (this.ReceivedFrame != null)
{
Delegate[] clients = this.ReceivedFrame.GetInvocationList();
foreach (Delegate del in clients)
{
try
{
del.DynamicInvoke(new object[] { this,
new StreamEventArgs(frame)} );
}
catch { }
}
}
this is the delegate:
public delegate void ReceivedFrameEventHandler(object sender, StreamEventArgs e);
public event ReceivedFrameEventHandler ReceivedFrame;
and this is how the function inside the control code-behind registers to it:
Camera.ReceivedFrame +=
new Camera.ReceivedFrameEventHandler(camera_ReceivedFrame);
I also tried
del.Method.Invoke(del.Target, new object[] { this, new StreamEventArgs(b) });
instead of
del.DynamicInvoke(new object[] { this, new StreamEventArgs(frame) });
but no luck
Does anyone know how I could fix this error or at least catch the error somehow and make the thread put the images on the panel once again?
This is because Gdi+ Image class is not thread safe. Hovewer you can avoid InvalidOperationException by using lock every time when you need to Image access, for example for painting or getting image size:
Image DummyImage;
// Paint
lock (DummyImage)
e.Graphics.DrawImage(DummyImage, 10, 10);
// Access Image properties
Size ImageSize;
lock (DummyImage)
ImageSize = DummyImage.Size;
BTW, invocation is not needed, if you will use the above pattern.
I had a similar problem with the same error message but try as I might, locking the bitmap didn't fix anything for me. Then I realized I was drawing a shape using a static brush. Sure enough, it was the brush that was causing the thread contention.
var location = new Rectangle(100, 100, 500, 500);
var brush = MyClass.RED_BRUSH;
lock(brush)
e.Graphics.FillRectangle(brush, location);
This worked for my case and lesson learned: Check all the reference types being used at the point where thread contention is occurring.
Seems to me, that the same Camera object is used several times.
E.g. try to use a new buffer for each received frame. It seems to me, that while the picture box is drawing the new frame, your capture library fills that buffer again. Therefore on faster machines this might not be an issue, with slower machines it might be an issue.
I've programmed something similar once, after each received frame, we had to request to receive the next frame and set the NEW frame receive buffer in that request.
If you can not do that, copy the received frame from the camera first to a new buffer and append that buffer to a queue, or just use 2 alternating buffers and check for overruns. Either use myOutPutPanel.BeginInvoke to call the camera_ReceivedFrame method, or better have a thread running, which checks the queue, when it has a new entry it calls mnyOutPutPanel.BeginInvoke to invoke your method to set the new buffer as image on the panel.
Furthermore, once you received the buffer, use the Panel Invoke Method to invoke the setting of the image (guarantee that it runs in the window thread and not the thread from your capture library).
The example below can be called from any thread (capture library or other separate thread):
void camera_ReceivedFrame(object sender, StreamEventArgs e)
{
if(myOutputPanel.InvokeRequired)
{
myOutPutPanel.BeginInvoke(
new Camera.ReceivedFrameEventHandler(camera_ReceivedFrame),
sender,
e);
}
else
{
myOutPutPanel.Image = e.Image;
}
}
I think this is multithreading problem
Use windows golden rule and update the panel in the main thread use panel.Invoke
This should overcome cross threading exception