Prev: ngen
Next: Is LinQ an option for this scenario?
From: Adam Clauss on 17 Feb 2010 16:34 We've recently been making use of a nice feature in WPF, that is that when you call Dispatcher.BeginInvoke(), you get back a DispatcherOperation object on which you can do various things: Change the priority of pending items, remove (cancel) an item, and (most important to this post): set an event for when the operation is completed. My concern is that there is a race condition here. Given the following code snippet: DispatcherOperation operation = Dispatcher.BeginInvoke(SomeMethod); operation.Completed += DispatcherOperation_Completed; Assuming it is called from a thread other than the main UI thread (which is kind of the whole point), I feel like it is possible for SomeMethod to have completed execution prior to me registering the handler for this. All it takes is this thread being context-switched out in between those two statements (or a multi-core machine). It seems like there should be a way to create a DispatcherOperation, set it's priority, setup event handlers... and THEN queue it up for execution. Am I missing something here? Is there a problem here, or am I just imagining one? Thanks -Adam
From: Peter Duniho on 17 Feb 2010 18:06 Adam Clauss wrote: > [...] > My concern is that there is a race condition here. Given the following > code snippet: > DispatcherOperation operation = Dispatcher.BeginInvoke(SomeMethod); > operation.Completed += DispatcherOperation_Completed; > > Assuming it is called from a thread other than the main UI thread (which > is kind of the whole point), I feel like it is possible for SomeMethod > to have completed execution prior to me registering the handler for > this. [...] > > Am I missing something here? Is there a problem here, or am I just > imagining one? There is a problem. You aren't imagining one. Personally, it seems to me that the DispatcherOperation class should automatically call newly subscribed handlers for the Aborted or Completed events if the operation has in fact already been Aborted or Completed. But, it doesn't. So, you have a couple of options: � Check the Status after adding your event handler, just in case: void MethodA() { DispatcherOperation operation = Dispatcher.BeginInvoke(SomeMethod); operation.Completed += DispatcherOperation_Completed; if (operation.Status == DispatcherOperationStatus.Completed) { DispatcherOperation_Completed(null, EventArgs.Empty); } } void SomeMethod() { // do some stuff } void DispatcherOperation_Completed(object sender, EventArgs e) { // your event handler stuff here } � Synchronize the block of code including the call to BeginInvoke() and adding the event handler, and also the SomeMethod() code, so that you are guaranteed that SomeMethod() cannot possibly complete until after your event handler is subscribed: readonly object _objLock = new object(); void MethodA() { lock (_objLock) { DispatcherOperation operation = Dispatcher.BeginInvoke(SomeMethod); operation.Completed += DispatcherOperation_Completed; } } void SomeMethod() { lock (_objLock) { // do some stuff } } void DispatcherOperation_Completed(object sender, EventArgs e) { // your event handler stuff here } Note that in the first approach, you still have a race condition. But it's somewhat more under your control, and it has the opposite result. That is, rather than potentially not being notified at all, the race condition could result in your code being notified twice. That is often an easier-to-solve problem. :) The second approach eliminates the race condition completely, at the cost of extra synchronization and all that implies. The thing I like least about the second approach is that there's a _theoretical_ possibility of deadlock, because there's a pair of locks (the explicit one, and another one implicit in the Dispatcher), and one thread acquires in the locks in one order (explicit, then implicit) while the other thread acquires the locks in the opposite order. Fortunately, the Dispatcher code looks well-written, and in particular the method invocation does _not_ occur while the implicit lock is being held, which ensures no actual deadlock happens due to the ordering of these particular locks. So the second approach should work just fine in practice. The above all assumes that you really want to use the Completed event. In fact, that may or may not be the best approach in a given scenario. There are others ways to identify the completion of the operation, and it's possible you'd find one of those ways preferable. Some options include: � Actually using Delegate.BeginInvoke() to execute a Dispatcher.Invoke() call, and pass a callback to the BeginInvoke() method to be notified when it completes: void MethodA() { Action<Delegate> action = DispatcherInvoke; // Executes DispatcherInvoke on a thread pool thread, // passing (Action)SomeMethod to the method, and calling // the DispatcherOperation_Completed method with "null" // when the DispatcherInvoke method completes action.BeginInvoke((Action)SomeMethod, DispatcherOperation_Completed, null); } void DispatcherInvoke(Delegate target) { Dispatcher.Invoke(target); } void DispatcherOperation_Completed(object state) { // your event handler stuff here } � Have your SomeMethod() method raise an event or execute some particular code that should happen when it's done: void MethodA() { Dispatcher.BeginInvoke((Action)SomeMethod); } void SomeMethod() { // do some stuff DispatcherOperation_Completed(); } void DispatcherOperation_Completed() { // your event handler stuff here } � Use an anonymous method to wrap the method being dispatched by the BeginInvoke() call: void MethodA() { Dispatcher.BeginInvoke((Action)delegate { SomeMethod(); DispatcherOperation_Completed(); }); } void SomeMethod() { // do some stuff } void DispatcherOperation_Completed() { // your event handler stuff here } Hopefully at least one of these possible solutions looks helpful to you. :) Pete
From: Adam Clauss on 17 Feb 2010 18:48 Peter Duniho wrote: > Hopefully at least one of these possible solutions looks helpful to > you. :) > > Pete Thanks Peter for confirming my suspicions. The anonymous method actually looks like a reasonably easy way to accomplish what we want while keeping our existing flow in place. -Adam
|
Pages: 1 Prev: ngen Next: Is LinQ an option for this scenario? |