1

How can I properly unsubscribe to an event and be sure that the called method is not called now ?

My problem is with this kind of code :

public class MyClassWithEvent
{
    public event EventHandler MyEvent;
    public int Field;
}

public class MyMainClass
{
    private MyClassWithEvent myClass;

    public void Start()
    {
        myClass.MyEvent += new EventHandler(doSomething);
    }

    public void Stop()
    {
        myClass.MyEvent -= new EventHandler(doSomething);
        myClass = null;
    }

    private void doSomething()
    {
        myClass.Field = 42;
    }
}

If myClass = null is called while doSomething is executing, instruction myClass.Field = 42 raise an error because myClass is null.

How can I be sure that doSomething is not executing before setting myClass = null ?

Edit:

Other example:

public void Stop()
{
    myClass.MyEvent -= new EventHandler(doSomething);

    // Can I add a function here to be sure that doSomething is not running ?

    myClass.Field = 101;
}

In that case, I will not be sure if myClass.Field is 42 or 101.

Edit2:

Apparently my question is not as simple as I thought. I will explain my precise case.

My code is :

public class MyMainClass
{
    object camera;//can be type uEye.Camera or DirectShowCamera
    bool isRunning = false;

    public void Start()
    {
        if (camera is uEye.Camera)
        {
            camera.EventFrame += new EventHandler(NewFrameArrived);
        }
        else if (camera is DirectShowCamera)
        {
            //other init
        }
        isRunning = true;
    }

    public void Stop()
    {
        if (camera is uEye.Camera)
        {
            camera.EventFrame -= new EventHandler(NewFrameArrived);
            camera.exit;
        }
        else if (camera is DirectShowCamera)
        {
            //other stop
        }
        isRunning = false;
    }

    public void ChangeCamera(object new camera)
    {
        if (isRunning)
            Stop()
        camera = new camera();
    }

    void NewFrameArrived(object sender, EventArgs e)
    {
        uEye.Camera Camera = sender as uEye.Camera;
        Int32 s32MemID;
        Camera.Memory.GetActive(out s32MemID);

        lock (_frameCameralocker)
        {
            if (_frameCamera != null)
                _frameCamera.Dispose();
            _frameCamera = null;
            Camera.Memory.ToBitmap(s32MemID, out _frameCamera);
        }

        Dispatcher.Invoke(new Action(() =>
        {
            lock (_frameCameralocker)
            {
                var bitmapData = _frameCamera.LockBits(
                    new System.Drawing.Rectangle(0, 0, _frameCamera.Width, _frameCamera.Height),
                    System.Drawing.Imaging.ImageLockMode.ReadOnly, _frameCamera.PixelFormat);

                if (_frameCamera.PixelFormat == System.Drawing.Imaging.PixelFormat.Format8bppIndexed)
                {
                    DeviceSource = System.Windows.Media.Imaging.BitmapSource.Create(
                                                        bitmapData.Width, bitmapData.Height, 96, 96, System.Windows.Media.PixelFormats.Gray8, null,
                                                        bitmapData.Scan0, bitmapData.Stride * bitmapData.Height, bitmapData.Stride);
                }

                _frameCamera.UnlockBits(bitmapData);

                if (OnNewBitmapReady != null)
                    OnNewBitmapReady(this, null);
            }
        }));
    }
}

And when I change the camera from uEye to directshow sometime I have a AccessViolationException in DeviceSource = System.Windows.Media.Imaging.BitmapSource.Create (method NewFrameArrived) because I try to create BitmapSource from an exited camera

A.Pissicat
  • 3,023
  • 4
  • 38
  • 93
  • Check if it's null before assigning the value? – ThePerplexedOne Oct 04 '16 at 12:36
  • My example is maybe too simple, I can have other case without using `myClass`. I will edit my question to show other case. – A.Pissicat Oct 04 '16 at 12:42
  • @ThePerplexedOne just checking doesnt solve a problem, because myClass can be assigned to null after checking and before using. Thats why I suggest to keep reference to myClass in local value. – tym32167 Oct 04 '16 at 12:47
  • You haven't shown the code that is raising the event or where/why this happens. Unless this is an asynchronous or parallel part of the program, the event should never be raised inbetween your statements. This means that the raising of the event in the first piece of code should either happen before `Stop`, or afterwards it would not call `doSomething`. In your last example, the event should not be raised until some later piece of code executes, *after* `Field` has been set to 101. – Lasse V. Karlsen Oct 04 '16 at 12:53
  • I've try to simplify my code to be clear, I thought that the problem was simple, apparently not. maybe a third edit will help. – A.Pissicat Oct 04 '16 at 13:03
  • You have to use the `lock` keyword in code where threads both read and write a variable. Forgetting to do so is a very basic threading race bug and causes random failure. – Hans Passant Oct 04 '16 at 13:09
  • I have a `lock` the problem seems to come from the `Invoke` and the read from memory. – A.Pissicat Oct 04 '16 at 13:22
  • @A.Pissicat - The updated code doesn't compile. It's missing a lot of code and it has many syntax errors. If you really want some good help you should provide a [mcve]. – Enigmativity Oct 04 '16 at 13:32
  • @A.Pissicat - What happens if you put `lock (_frameCameralocker)` into the `Start` and `Stop` methods? – Enigmativity Oct 04 '16 at 13:44
  • It seems to work :-) The exception don't raise all the time, but I don't see it since I locked the Stop. Thank you – A.Pissicat Oct 04 '16 at 13:54
  • 1
    @A.Pissicat you could still run in to a problem where the dispatcher was queued before the `Stop()` lock happened. You just need to put a `IsRunning` check in the dispatcher, see my answer. – Scott Chamberlain Oct 04 '16 at 13:57

3 Answers3

2

From your updated question, the only thing you need to do is just lock the Stop() action from the same lock as the Dispatcher.Invoke

public void Stop()
{
    lock(_frameCameralocker)
    {
        if (camera is uEye.Camera)
        {
            camera.EventFrame -= new EventHandler(NewFrameArrived);
            camera.exit;
        }
        else if (camera is DirectShowCamera)
        {
            //other stop
        }
        isRunning = false;
    }
}

This will make sure all NewFrameArrived calls finished or have not started before you create the new camera. Then inside the dispatcher check to see if you are running or not just in case a frame was queued before a Stop() call was started and completed.

    Dispatcher.Invoke(new Action(() =>
    {
        lock (_frameCameralocker)
        {
            if(!isRunning)
                return;

            var bitmapData = _frameCamera.LockBits(
                new System.Drawing.Rectangle(0, 0, _frameCamera.Width, _frameCamera.Height),
                System.Drawing.Imaging.ImageLockMode.ReadOnly, _frameCamera.PixelFormat);

            if (_frameCamera.PixelFormat == System.Drawing.Imaging.PixelFormat.Format8bppIndexed)
            {
                DeviceSource = System.Windows.Media.Imaging.BitmapSource.Create(
                                                    bitmapData.Width, bitmapData.Height, 96, 96, System.Windows.Media.PixelFormats.Gray8, null,
                                                    bitmapData.Scan0, bitmapData.Stride * bitmapData.Height, bitmapData.Stride);
            }

            _frameCamera.UnlockBits(bitmapData);

            if (OnNewBitmapReady != null)
                OnNewBitmapReady(this, null);
        }
    }));
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
1

Maybe a good use for Monitor ?

The idea is that you use a lock to make sure you are not using the same resource twice at the (almost) same time :

public class MyClassWithEvent
{
    public event EventHandler MyEvent;
    public int Field;
}

public class MyMainClass
{
    private MyClassWithEvent myClass;
    private object mylock;

    public void Start()
    {
        myClass.MyEvent += new EventHandler(doSomething);
    }

    public void Stop()
    {
        myClass.MyEvent -= new EventHandler(doSomething);
        Monitor.Enter(mylock); //If somebody else already took the lock, we will wait here
        myClass = null;
        Monitor.Exit(mylock); //We release the lock, so others can access it
    }

    private void doSomething()
    {
        Monitor.Enter(mylock);
        if myClass != null
        {
            myClass.Field = 42;
        }
        Monitor.Exit(mylock);
    }
}

EDIT

According to comments, Lock would be a better use (actually a short-hand for Monitor) :

public class MyClassWithEvent
{
    public event EventHandler MyEvent;
    public int Field;
}

public class MyMainClass
{
    private MyClassWithEvent myClass;
    private object mylock;

    public void Start()
    {
        myClass.MyEvent += new EventHandler(doSomething);
    }

    public void Stop()
    {
        myClass.MyEvent -= new EventHandler(doSomething);
        lock (mylock) //If somebody else already took the lock, we will wait here
        {
            myClass = null;
        } //We release the lock, so others can access it
    }

    private void doSomething()
    {
        lock(mylock)
        {
            if myClass != null
            {
                myClass.Field = 42;
            }
        }
    }
}
Community
  • 1
  • 1
Martin Verjans
  • 4,675
  • 1
  • 21
  • 48
0

Instead

myClass.Field = 42;

Do

val local = myClass;
if (local != null)
    local.Field = 42;
tym32167
  • 4,741
  • 2
  • 28
  • 32
  • I edited my question with another case, your solution will not work with the second case. I'm searching for a synchronization mechanism – A.Pissicat Oct 04 '16 at 12:48
  • In that case take a look at ```ReaderWriterLockSlim``` I think it can be useful for you, because you are trying to control write operations. MSDN link https://msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim(v=vs.110).aspx – tym32167 Oct 04 '16 at 13:12