.NET Forum / .NET Framework / New Users / July 2007
Thread safety of events
|
|
Thread rating:  |
Armin Zingler - 10 Jul 2007 23:19 GMT Hi,
I have an object O1 that executes code in thread T1. O1 raises events. There is an object O2 that runs in thread T2. O2 catches O1's events.
Question: What happens if O2 is detaching the event handler at the same time as O1 is raising the event? Do I have to handle this special situation explicitly? Honestly I haven't read/heard that anybody ever cared about this. It would be pretty much work to do because every event handling object would have to prevent the event raiser from raising the event while detaching it's own handlers. Not a good OO way also. I know the "instance members are not thread safe" part of the Delegate/Multicastdelegate documentation, but I also think that Events are a basic element in the runtime's infrastructure, so maybe I don't have to handle the situation explicitly?
Armin
Peter Duniho - 11 Jul 2007 00:31 GMT > I have an object O1 that executes code in thread T1. O1 raises events. > There is an object O2 that runs in thread T2. O2 catches O1's events. [quoted text clipped - 3 lines] > is raising the event? Do I have to handle this special situation > explicitly? You sort of do.
First, keep in mind that when an event is raised in O1, even though O2 normally runs in T2, its event handlers are executed on T1, where O1 raised the event. You probably already knew that, but just want to clarify.
Now, that means you do have the potential of having O2 running on T2 unsubscribing at the same time that O1 running on T1 raises the event. There's a lot of event-based code out there that doesn't handle this situation, and usually it's okay because everything is actually running on the same thread. But when the potential for this condition exists, at a minimum you need to do something like this:
OnRaiseEvent(...) { EventType eventInstance = _eventField;
if (eventInstance != null) { eventInstance(...); } }
This copies the event member field to the local variable eventInstance so that if the member field is set to null between the time it's checked for null and the time it's actually used, that's not an issue. By copying it, you ensure that what's checked for null is your local variable, not the member field that could change.
Now, there is the separate issue of O2 having unsubscribed while O1 now has a reference to it in its local variable. This means that even though O2 has unsubscribed, it's not quite done yet. O1 still has a reference to O2, via the local variable, and O2 will still be called when the event variable is executed.
Whether this is a problem depends a lot on the design of O2 (and even of O1 to some extent). In particular, if O2 can still respond to an event, even though you have discarded it somewhere else, then there's really no problem. On the other hand, if you have a situation where O2 is disposable, and as part of disposing that's where you unsubscribe, and something else that is released as part of disposing is required in order to handle the event (get all that? :) ), then yes...you need to be able to sychronize access to the thing that is required, and in the event handler make an explicit check to verify that your object is in fact still in a state in which it can handle the event. If it's not, then you would just ignore the event as it if never happened.
Once the O1 event raising method returns, the value in the local variable is released, and since the O1 instance member containing the event reference no longer refers to O2 either, you can at that point be assured the event handler will not be called again. But you do need to be able to handle correctly the transient condition in which you think you've unsubscribed but could still get one more raised event.
Pete
Peter Duniho - 11 Jul 2007 00:49 GMT > [...] > On the other hand, if you have a situation where O2 is disposable, and > as part of disposing that's where you unsubscribe, and something else > that is released as part of disposing is required in order to handle the > event (get all that? :) ), then yes...you need to be able to sychronize > access to the thing that is required To clarify:
The above is an example of where you'd need to synchronize access. It's not meant to be the only case. Obviously, putting the unsubscribe operation somewhere other than the Dispose() method doesn't necessarily fix things. You could still manage to successfully unsubscribe, then call Dispose just after the event member field has been copied in the other thread, but all before the event handlers have been called, whether the operation to unsubscribe happens in the Dispose or not.
The key here is not where you unsubscribe, but whether disposing the object somehow invalidates some data structure that is required in order for the object to successfully handle the event.
Pete
Armin Zingler - 11 Jul 2007 01:17 GMT > On Tue, 10 Jul 2007 16:31:59 -0700, Peter Duniho > <NpOeStPeAdM@nnowslpianmk.com> wrote: [quoted text clipped - 20 lines] > object somehow invalidates some data structure that is required in > order for the object to successfully handle the event. I think I understand. :-) And yes, I know that the event handler is execute in the same thread.
Though, what you described is not the main problem, sorry. :-) I can handle the states and whatever. What I worried about was the fact, that the invocation list is modified by removing one event handler (in T2) at the same time as it is processed in order to raise the event (in T1). Raising the event might lead to an exception due to the manipulation from the other thread (T2).
In other words, do I have to do the following? (example only; shall only show the concept)
Class C1 Public Event Progress() Public Locker As New Object Public Sub DoWork() '... SyncLock Locker RaiseEvent Progress() End SyncLock '... End Sub End Class
Class C2 Public Sub DoWork() Dim O1 As New C1 addhandler O1.Progress,... '... SyncLock O1.Locker removehandler O1.Progress,... End SyncLock End Sub End Class
In this case, it's ensured that the event will not be raised (in T1) while the handler is being removed (in T2).
Armin
Peter Duniho - 11 Jul 2007 02:04 GMT > [...] > Though, what you described is not the main problem, sorry. :-) I can [quoted text clipped - 3 lines] > Raising the event might lead to an exception due to the manipulation > from the other thread (T2). I know. That is why in the code I posted, the invocation list is copied into a local variable before invoking the handlers.
> [...] > In this case, it's ensured that the event will not be raised (in T1) > while the handler is being removed (in T2). Yes, that is another way to do it. I don't like exposing a synchronization object from one class to another class, but if you choose to do it that way, I believe it should work fine. The big problem with that mechanism is that you wind up requiring every subscriber to the event to use the synchronization object. Since events are supposed to be general purpose, this is an unusual design, and one that is likely to lead to maintenance issues in the future.
But if you are the only person who will ever maintain the code and the event is only to be used in this one very specific situation, it is a risk that you can probably afford.
You do need to synchronize access somehow though, however you do it.
Pete
John Saunders [MVP] - 11 Jul 2007 00:37 GMT > Hi, > > I have an object O1 that executes code in thread T1. O1 raises events. > There is an object O2 that runs in thread T2. O2 catches O1's events. O2 doesn't "catch" events at all. When O1 raises an event, if O2 has a delegate in the handler list of the event, then the handler in O2 will be called in thread T1.
 Signature John Saunders [MVP]
Armin Zingler - 11 Jul 2007 00:49 GMT > "Armin Zingler" <az.nospam@freenet.de> wrote in message > news:ed2Vhb0wHHA.3340@TK2MSFTNGP04.phx.gbl... [quoted text clipped - 5 lines] > > O2 doesn't "catch" events at all. No, it does. I wouldn't say it if it didn't. "Catch" is short for "contains a procedure that is the event handler of an event of" - I thought, this is a common term.
> When O1 raises an event, if O2 has > a delegate in the handler list of the event, then the handler in O2 > will be called in thread T1. Right.
Armin
Peter Duniho - 11 Jul 2007 01:54 GMT >> O2 doesn't "catch" events at all. > > No, it does. I wouldn't say it if it didn't. "Catch" is short for > "contains a procedure that is the event handler of an event of" - I > thought, this is a common term. In .NET, "catch" is typically used for exceptions, while "handles" is typically used for events.
While I might find less point in quibbling over the semantics than John, if we're going to argue about common usage, John's point is correct. "Catch" is not at all a common term used to describe event handling.
Pete
William Stacey [C# MVP] - 11 Jul 2007 03:26 GMT I agree. You throw and catch exceptions. We "invoke" (i.e. raise or call) events. catch is not normal usage for events. The thread raising the event executes/runs the handler (delegate) code (unless you have some other special need to invoke handler(s) delegates on other threads).
 Signature William Stacey [C# MVP]
| >> O2 doesn't "catch" events at all. | > [quoted text clipped - 10 lines] | | Pete Armin Zingler - 11 Jul 2007 04:51 GMT > I agree. You throw and catch exceptions. We "invoke" (i.e. raise or > call) events. catch is not normal usage for events. The thread > raising the event executes/runs the handler (delegate) code (unless > you have some other special need to invoke handler(s) delegates on > other threads). Strictly speaking yes, but the context was clear, so... Was just a variation of "handle", "receive", "listen to"(, "catch"). Please note that English is not my native language, therefore I thought it was clear in the context. Really, "catch an event" is ambiguous? Ok, I'll remember. :-)
Armin
William Stacey [C# MVP] - 11 Jul 2007 22:31 GMT | Really, "catch an event" is ambiguous? Ok, I'll remember. :-) IMO, yes. Because your not catching anything, nor is there any context switch happening - the calling thread is just running some delegates in a list (top to bottom).
Armin Zingler - 11 Jul 2007 23:22 GMT > | Really, "catch an event" is ambiguous? Ok, I'll remember. :-) > > IMO, yes. Because your not catching anything, nor is there any > context switch happening - the calling thread is just running some > delegates in a list (top to bottom). In the German language, it is ok to use variations and different words for the same thing as long as the context is clear and it is unambigiuous. What is done here we would call splitting hairs. More important is to understand each other and not to insist on the correct technical terms - as long as the discussion is not about the correct terms. "Catch" was more metaphorically spoken (catch it, handle it, grab it, get it, react on it or whatever). I feel sorry that I really didn't know that in the English language only the one technical term "handle" is allowed. Sorry, but I'm restricted by my school English (which is some years old). Please take this into consideration.
I also thought that it is valid to say "fire" an event. Now that I know that I always must use the correct technical term "raise" I will do this invariably in future.
Armin
Armin Zingler - 11 Jul 2007 04:34 GMT > On Tue, 10 Jul 2007 16:49:27 -0700, Armin Zingler > <az.nospam@freenet.de> wrote: [quoted text clipped - 12 lines] > correct. "Catch" is not at all a common term used to describe event > handling. I wrote "catch an event", so what do you think I am referring to? An event or an exception? Mind the context. Too unambigious to misunderstand.
Armin
Peter Duniho - 11 Jul 2007 05:32 GMT > I wrote "catch an event", so what do you think I am referring to? An > event or an exception? Mind the context. Too unambigious to > misunderstand. Sigh.
It's not a question of whether we understood what you meant. It's a question of whether you wrote the correct thing.
People post all sorts of crazy things here, and those of us trying to help them are tasked with deciphering what they wrote. Sometimes we can figure it out, sometimes we can't. Sometimes we think we figured it out, but we didn't.
You using the word "catch" when what you really should have written was "handle" isn't the worst thing we've ever seen around here, by any stretch of the imagination. I doubt anyone reading your post had any trouble understanding what you meant. But that doesn't make it correct.
IMHO, it was a waste of bandwidth for John to comment on the misuse. However, he didn't actually post anything that was incorrect, and it's an even bigger waste of bandwidth for you to argue about whether "catch" is the right verb to use or not. I doubt you can find a single page in MSDN that uses the word "catch" in connection with having the handler for an event called when that event is raised.
Pete
Armin Zingler - 11 Jul 2007 11:50 GMT > On Tue, 10 Jul 2007 20:34:48 -0700, Armin Zingler > <az.nospam@freenet.de> wrote: [quoted text clipped - 6 lines] > > It's not a question of whether we understood what you meant. No, it is. Period.
Armin
Jon Skeet [C# MVP] - 11 Jul 2007 19:42 GMT > I have an object O1 that executes code in thread T1. O1 raises events. > There is an object O2 that runs in thread T2. O2 catches O1's events. > > Question: > What happens if O2 is detaching the event handler at the same time as O1 > is raising the event? Do I have to handle this special situation explicitly? You should do, yes - at least if you foresee people subscribing to or unsubscribing from the events from a different thread.
> Honestly I haven't read/heard that anybody ever cared about this. See http://pobox.com/~skeet/csharp/threads/lockchoice.shtml
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet If replying to the group, please do not mail me too
Armin Zingler - 11 Jul 2007 19:54 GMT > Armin Zingler <az.nospam@freenet.de> wrote: > > I have an object O1 that executes code in thread T1. O1 raises [quoted text clipped - 12 lines] > > See http://pobox.com/~skeet/csharp/threads/lockchoice.shtml Ok, thanks. I'll have a look.
Armin
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|