【问题标题】:How to properly unsubscribe to an event如何正确取消订阅事件
【发布时间】:2017-02-12 15:05:23
【问题描述】:

如何正确取消订阅事件并确保现在不调用被调用的方法?

我的问题是这种代码:

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;
    }
}

如果在执行doSomething 时调用myClass = null,则指令myClass.Field = 42 将引发错误,因为myClass 为空。

如何确定doSomething 在设置myClass = null 之前没有执行?

编辑:

其他例子:

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;
}

在这种情况下,我不确定myClass.Field 是 42 还是 101。

编辑2:

显然我的问题并不像我想象的那么简单。我会解释我的具体情况。

我的代码是:

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);
            }
        }));
    }
}

当我将摄像头从 uEye 更改为 directshow 时,我在 DeviceSource = System.Windows.Media.Imaging.BitmapSource.Create(方法 NewFrameArrived)中有一个 AccessViolationException,因为我尝试从退出的摄像头创建 BitmapSource

【问题讨论】:

  • 赋值前检查是否为null?
  • 我的例子可能太简单了,不用myClass也可以有其他情况。我将编辑我的问题以显示其他情况。
  • @ThePerplexedOne 只是检查并不能解决问题,因为 myClass 可以在检查之后和使用之前分配为 null。这就是为什么我建议在本地值中保留对 myClass 的引用。
  • 您没有显示引发事件的代码或发生这种情况的位置/原因。除非这是程序的异步或并行部分,否则不应在语句之间引发事件。这意味着在第一段代码中引发事件应该发生在Stop 之前,或者之后它不会调用doSomething。在您的最后一个示例中,直到稍后的代码执行,在 Field 已设置为 101 之后,才应引发事件。
  • @A.Pissicat 您仍然可能遇到调度程序在Stop() 锁定发生之前排队的问题。您只需要在调度程序中输入IsRunning 支票,请参阅我的答案。

标签: c#


【解决方案1】:

根据您更新的问题,您唯一需要做的就是将Stop() 操作与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;
    }
}

这将确保在您创建新相机之前所有NewFrameArrived 调用都已完成或尚未开始。然后在调度程序内部检查您是否正在运行,以防在 Stop() 调用开始和完成之前帧已排队。

    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);
        }
    }));

【讨论】:

    【解决方案2】:

    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);
            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);
        }
    }
    

    编辑

    根据 cmets,Lock 会更好用 (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;
                }
            }
        }
    }
    

    【讨论】:

    • 无需使用Monitor,只需使用lock即可。
    • 使用锁或尝试 finally 块来锁定对象。
    • 我用自己的锁(见edit2)而不是Monitor,我现在没有异常
    【解决方案3】:

    相反

    myClass.Field = 42;
    

    做

    val local = myClass;
    if (local != null)
        local.Field = 42;
    

    【讨论】:

    • 我用另一种情况编辑了我的问题,您的解决方案不适用于第二种情况。我正在寻找一种同步机制
    • 在这种情况下,请查看ReaderWriterLockSlim 我认为它对您很有用,因为您正在尝试控制写入操作。 MSDN链接msdn.microsoft.com/en-us/library/…
    猜你喜欢
    • 2013-04-15
    • 1970-01-01
    • 1970-01-01
    • 2018-01-05
    • 2017-07-16
    • 2014-02-15
    • 1970-01-01
    • 2017-04-25
    • 1970-01-01
    相关资源
    最近更新 更多