views:

93

answers:

3

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 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....

+1  A: 

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.

Jon Skeet
thanks ill check this out right away. I have used delegates before to call a method but that was at my "Real job" and i don't have the code here so not sure what I did there... been a while.
Codejoy
+1  A: 

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.

VinayC
+1  A: 

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);
        }
    }
}
Hans Passant
okay I like how this looks and makes me feel more warm and fuzzy. I see the issue with too many threads and I have no way around it that I can figure. The timers I am using (The main one at least) is a windows form timer. The other ones are yes the threading timer and the delegates. What I am trying to do sounds easy enough but apparently there is far more going on under the hood than thought. I guess I can use this new control on my form and still access the image via that Image property?
Codejoy
Yes, that was the intention. Try it.
Hans Passant