S
Scott Gifford
I have a question about concurrency and delegates.
Say I have a class roughly like this:
public class Class1
{
public delegate void MyEventHandler();
private event MyEventHandler onMyEvent;
public event OnMyEvent
{
add
{
onMyEvent += value;
}
remove
{
onMyEvent -= value;
}
}
private void MyEventHappened()
{
if (onMyEvent != null)
onMyEvent();
}
If this class is used by multiple threads, it looks like there is a
race condition. If the last event is removed between the "if
(onMyEvent != null)" and the "onMyEvent()", then "onMyEvent()" will
throw a Null Reference Exception, right?
So the obvious fix is to use locks. The code for the event property
is obvious, and MyEventHappened now looks like this:
private void MyEventHappened()
{
lock(this)
{
if (onMyEvent != null)
onMyEvent();
}
}
But now we're calling the delegate with the lock held. This seems bad
in general, since the event could start doing some long-running thing.
A more immediate problem happens if that event handler uses
form.Invoke, then the UI thread tries to call back into Class1 to do
something that requires a lock. Instant deadlock!
To fix the race condition, then, we have to either catch and ignore
the Null reference exception, or make a copy of it while it's locked,
then operate on our copy (which can't change behind our backs):
private void MyEventHappened()
{
event MyEventHandler copyOfOnMyEvent;
lock(this)
{
copyOfOnMyEvent = onMyEvent;
}
if (copyOfOnMyEvent != null)
copyOfOnMyEvent();
}
(does that work? Does "copyOfOnMyEvent = onMyEvent" make a copy of
onMyEvent, or just store a reference to it?)
My question is, do people actually do this? It seems quite tedious,
and I've never seen it in sample code. Is there some reason I'm not
seeing why this isn't really a race condition? Is there some other
way to deal with this?
Thanks!
---Scott.
Say I have a class roughly like this:
public class Class1
{
public delegate void MyEventHandler();
private event MyEventHandler onMyEvent;
public event OnMyEvent
{
add
{
onMyEvent += value;
}
remove
{
onMyEvent -= value;
}
}
private void MyEventHappened()
{
if (onMyEvent != null)
onMyEvent();
}
If this class is used by multiple threads, it looks like there is a
race condition. If the last event is removed between the "if
(onMyEvent != null)" and the "onMyEvent()", then "onMyEvent()" will
throw a Null Reference Exception, right?
So the obvious fix is to use locks. The code for the event property
is obvious, and MyEventHappened now looks like this:
private void MyEventHappened()
{
lock(this)
{
if (onMyEvent != null)
onMyEvent();
}
}
But now we're calling the delegate with the lock held. This seems bad
in general, since the event could start doing some long-running thing.
A more immediate problem happens if that event handler uses
form.Invoke, then the UI thread tries to call back into Class1 to do
something that requires a lock. Instant deadlock!
To fix the race condition, then, we have to either catch and ignore
the Null reference exception, or make a copy of it while it's locked,
then operate on our copy (which can't change behind our backs):
private void MyEventHappened()
{
event MyEventHandler copyOfOnMyEvent;
lock(this)
{
copyOfOnMyEvent = onMyEvent;
}
if (copyOfOnMyEvent != null)
copyOfOnMyEvent();
}
(does that work? Does "copyOfOnMyEvent = onMyEvent" make a copy of
onMyEvent, or just store a reference to it?)
My question is, do people actually do this? It seems quite tedious,
and I've never seen it in sample code. Is there some reason I'm not
seeing why this isn't really a race condition? Is there some other
way to deal with this?
Thanks!
---Scott.