如何正确取消订阅事件
Posted
技术标签:
【中文标题】如何正确取消订阅事件【英文标题】: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
支票,请参阅我的答案。
【参考方案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/…以上是关于如何正确取消订阅事件的主要内容,如果未能解决你的问题,请参考以下文章