开发者

Will a call made from Dispatcher.BeginInvoke() always commence after the current method has completed?

I've got an extension method 开发者_如何学Gothat I use to fire PropertyChanged notifications that looks something like this:

    public static TRet RaiseIfChanged<TObj, TRet>(this TObj target, Expression<Func<TObj, TRet>> property, TRet newValue)
        where TObj : AlantaViewModelBase
    {
        var propertyInfo = (property.Body as MemberExpression).Member as PropertyInfo;
        var propertyValue = propertyInfo.GetValue(target, null);

        if (!EqualityComparer<TRet>.Default.Equals((TRet)propertyValue, (TRet)newValue))
        {
            // Marshal this to the dispatcher so that the RaisePropertyChanged() notification happens after 
            // the value is actually set.
            Deployment.Current.Dispatcher.BeginInvoke(() => target.RaisePropertyChanged(propertyInfo.Name));
        }
        return newValue;
    }

It gets used like this:

    private bool isBusy;
    public bool IsBusy
    {
        get { return isBusy; }
        set { isBusy = this.RaiseIfChanged(p => p.IsBusy, value); }
    }

My question is about the Dispatcher.BeginInvoke() that I use to actually call the target.RaisePropertyChanged(). Given that the property won't actually change until the extension method completes, I need to be confident that the event won't actually get raised until after the property's value has been set. Can I be confident that this will always be the case? Or are there instances when the event will get fired before the property has changed? Or is there a better way to handle this?


You have no such guarantee. The exact time the RaisePropertyChanged() method runs greatly depends on the state of the UI thread. And if it just happens to be ready to look at the invoke queue for invoke targets to execute at the exact time your worker thread calls RaiseIfChanged and the worker thread loses it processor quantum then, yes, that call is going to be made before the worker can return and set the property value. Unlikely, but the road of good threading intentions is littered with roadkill like this.

You have to assign the property before raising the event. The un-dry test-and-set is pretty simple. Keep dry but ugly by using PropertyInfo.SetValue() or passing the backing variable by reference. Personally I wouldn't, using reflection to read the property value is an easy three orders of magnitude slower than just coding this directly.


Your code will not guarantee the execution order. That is, the event could be signaled before the property is set. On a single-processor machine, for example, it wouldn't be surprising to see the dispatcher just execute the code synchronously. In that case, the event would be signaled before returning from the method.

On a multicore or multiprocessor machine, all bets are off. I suspect that you'll find that sometimes the event is signaled before the property is changed, and sometimes it's not.

The safe way to do this is:

set 
{
    var oldValue = isBusy;
    isBusy = value;
    this.RaiseIfChanged(oldValue, value); 
}

Of course, that requires changes to your extension method . . .


Dispatcher.BeginInvoke() schedules a new thread to run the lambda. That thread will run as soon as there is processor time. The RaisePropertyChanged() method, I assume, manually fires an event, which in .NET is usually implemented with a synchronous MulticastDelegate. If this is all the case, the event handler MAY run before the actual property changes; whether it will or not depends on hardware details beyond your control, but the short of it is, you cannot guarantee it.

The way you should do something like this is to store the original value, perform the assignment, THEN raise the event if there really was a change in the value. KISS: you could redefine RaiseIfChanged to take the old value instead of a projection, or you could perform the assignment within RaiseIfChanged before firing the event.


There is no assumption you can make about when it will execute. It will execute either immediately (in the case that you're running from the UI thread) or at any point in the future.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜