From: Adam Clauss on
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
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
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?