InvokeRequired is true on PictureBox. How to deal with this?
I had another question on my PictureBox calls giving me 3 kinds of errors, some great answers came in particularly from Conrad Frix. So it led me to figure out where my problem is, but now to fix it I am not 100% sure on.
Basically I have a Windows Form timer that is checking for some event to be true, if it is, then it tells the system开发者_如何学Python to send some data out 2 seconds after said event (a value ) is past some threshold.
I think all the timers I have is creating a nasty race condition with my PictureBox that I use in several places to get the image from:
new Bitmap(myPicBox.Image);
etc...
I read somewhere that the interval on the timer should be at least 50. Set that from 33. I found out I can do a picCapture.InvokeRequired to see if its going to basically die. I know I need to use a delegate but only ever used those to set something... not to get an image from.... not sure how to set that up... I know what is indeed causing it, it is this combination of code:
private void timer1_Tick(object sender, EventArgs e)
{
if(someCOnditionTrue)
{
TimerCallback tc = new TimerCallback(sendDataFast); //only
//doing all this so i can have the method run two seconds after
// the condition is detected to be true.
System.Threading.Timer t = new System.Threading.Timer(tc, null, 2000, Timeout.Infinite);
}
}
void sendDataFast(Object stateObject)
{
//using this so the execution is not haulted while the sending of data takes place.
EmergencyDelegate delEmergency =
new EmergencyDelegate(mic.sendEmergencyData);
Image imgclone;
if (picCapture.InvokeRequired)
{
Console.WriteLine("HFS Batman! its going to die ");
}
lock (lockObject2) //i admit no clue what im doing here and doesn't seem to help.
{
Image img = picCapture.Image;
imgclone = (Image)img.Clone();
}
delEmergency.BeginInvoke(imgclone, null, null); //deep in the call to
//sendEmergencyData i get the **ParameterNotValid** almost everytime.
imgclone.Dispose(); //to free memory?
}
As per my previous question, no longer seem to get the memory issues or other errors in the timer1_tick event... (out of memory error was one).
I think the biggest issue is how can I handle the picCapture.InvokeRequired when I need its image data? I am certain its the threading timer call inside the timer1_click I do that is causing this....
As its name suggests, InvokeRequired
means you need to call Invoke
(or BeginInvoke
) when accessing the control.
Note that this is Control.Invoke
/Control.BeginInvoke
, not the Invoke/BeginInvoke
which are present in delegates... although you'll need a delegate in order to call Invoke
/BeginInvoke
, just to add more confusion to the mix.
See the Windows Forms section of my threading tutorial for more details. The overall tutorial could do with updating, but I believe this bit is okay. In other situations you may also want to consider using BackgroundWorker
, but I don't think that's likely to be relevant for you in this particular case.
I think that you have got a wrong understanding about InvokeRequired. InvokeRequired indicates that the current thread is not the same as the UI thread and it will not be safe to access the control state now. If such is the case then you have to use Control.Invoke
to marshal call to the UI thread and then access the control state. Read here on MSDN for more info.
In your case, unless the PictureBox image is changing, I would suggest that you rather make a clone of the image upfront and use that. Otherwise you need to use Control.Invoke
.
You've got too many threads going to bring this to a good end. Both the Timer and the delegate's BeginInvoke() method will use a threadpool thread. The problem is that the PictureBox.Image property is only partially thread-safe. It can be accessed by only one thread at a time. Your code will die with an exception when the image is painted by the UI thread at the exact same time your code is calling the Clone() method.
Your lock statement doesn't solve the problem, the PictureBox is accessing the Image property without using that same lock. I would strongly recommend getting rid of the threading first, use a System.Windows.Forms.Timer instead of a System.Threading.Timer. It's Tick event is raised on the UI thread. That will however make the UI thread unresponsive while the event is running, it depends how long it takes whether that's noticeable to the user. More than, say, 100 milliseconds gets to be a problem.
The only other approach is to try to make the PictureBox control thread-safe. That's possible to some degree. Add a new class to your project and paste the code shown below. Compile. Drop the new control from the top of the toolbox onto your form, replacing the existing PB. Beware that this is only a partial solution, displaying an animated GIF or using the ImageLocation property will still bomb. Use the provided Clone method instead of calling Clone on the Image property.
using System;
using System.Drawing;
using System.Windows.Forms;
class MyPictureBox : PictureBox {
private object locker = new object();
public new Image Image {
get { return base.Image; }
set { lock (locker) { base.Image = value; } }
}
public Image Clone() {
lock (locker) {
return (this.Image != null) ? (Image)this.Image.Clone() : null;
}
}
protected override void OnPaint(PaintEventArgs pe) {
lock (locker) {
base.OnPaint(pe);
}
}
}
精彩评论