Possible solution to issue with multiple UI dispat

2019-09-17 10:02发布

I'm having a problem with using multiple UI dispatchers to modify an list that is bound to the UI. The method with just exit when it hits the first dispatcher. If I wrap the entire method in the dispatcher it works, but I have another solution but I'm not sure it's appropriate:

Basically, I have a socket listening in a never-ending loop for network commands from a media device. When it finds one, it calls ProcessCommand.

That function calls one of 50+ methods to process the specific commands. Those functions store the internal state, but mainly raise events my main application can subscribe to so it knows when something like the volume has changed and I can update the UI.

These work pretty well, except the one case noted at the start where I need to modify a state object that is bound using several dispatchers in the same method and it not working, and wrapping the method in one big dispatcher seems to work.

Another solution I found is to run the ProcessCommand running in the socket background listener on the UI dispatcher, ie:

                    CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
                    { 
                       ProcessCommand(command);
                    });

This will then trickle down into all of the individual 50+ ProcessCommandXYZ methods, and also in my main app where I subscribe to these events on multiple pages I currently have to use a UI dispatcher on every one, so something like:

Socket listener background task > {DispatcherUI ProcessCommand} > ProcessVolumeCommand > Raise OnVolumeChangedEvent > Subscriber updates UI

Would make it so I don't need to put each event subscriber into a dispatcher, but most importantly fixes problem I have with multiple dispatchers in one method:

Using Dispatcher correctly in event to update UI

Does this seem like a good solution?

1条回答
Fickle 薄情
2楼-- · 2019-09-17 10:57

Here is my solution for such situations when you subscribed to event that raises on Non-ui thread.

//subscribe to event
//handler (OnPlayerVolumeChanged) will be invoked on subscriber (UI) thread
var subscriptionToken = EventSubscription.FromEvent<VolumeChangedEventArgs>(
                            handler => _player.VolumeChanged += handler,
                            handler => _player.VolumeChanged -= handler,
                            OnPlayerVolumeChanged,
                            HandlerThreadOption.SubscriberThread);

//unsubscribe
subscriptionToken.Unsubscribe();

And here is implementation:

public enum HandlerThreadOption
{
    PublisherThread,
    SubscriberThread,
    BackgroundThread
}

public class EventSubscriptionToken : IDisposable
{
    private readonly Action _unsubscribe;


    public EventSubscriptionToken(Action unsubscribe)
    {
        _unsubscribe = unsubscribe;
    }


    public void Unsubscribe()
    {
        _unsubscribe();
    }


    void IDisposable.Dispose()
    {
        Unsubscribe();
    }
}


public static class EventSubscription
{
    public static EventSubscriptionToken FromEvent<TEventArgs>(
        Action<EventHandler<TEventArgs>> addHandler,
        Action<EventHandler<TEventArgs>> removeHandler,
        EventHandler<TEventArgs> handler,
        HandlerThreadOption threadOption)
    {
        var threadSpecificHandler = GetHandler(handler, threadOption);
        addHandler(threadSpecificHandler);

        return new EventSubscriptionToken(() => removeHandler(threadSpecificHandler));
    }

    public static EventSubscriptionToken FromEvent(
        Action<EventHandler> addHandler,
        Action<EventHandler> removeHandler,
        EventHandler handler,
        HandlerThreadOption threadOption)
    {
        var threadSpecificHandler = GetHandler(handler, threadOption);
        addHandler(threadSpecificHandler);

        return new EventSubscriptionToken(() => removeHandler(threadSpecificHandler));
    }


    private static EventHandler<T> GetHandler<T>(EventHandler<T> handler, HandlerThreadOption threadOption)
    {
        switch (threadOption)
        {
            case HandlerThreadOption.PublisherThread:
                return handler;
            case HandlerThreadOption.SubscriberThread:
                return GetCurrentThreadExecutionStrategy(handler);
            case HandlerThreadOption.BackgroundThread:
                return GetBackgroundThreadExecutionStrategy(handler);
            default:
                throw new ArgumentOutOfRangeException("threadOption");
        }
    }

    private static EventHandler GetHandler(EventHandler handler, HandlerThreadOption threadOption)
    {
        switch (threadOption)
        {
            case HandlerThreadOption.PublisherThread:
                return handler;
            case HandlerThreadOption.SubscriberThread:
                return GetCurrentThreadExecutionStrategy(handler);
            case HandlerThreadOption.BackgroundThread:
                return GetBackgroundThreadExecutionStrategy(handler);
            default:
                throw new ArgumentOutOfRangeException("threadOption");
        }
    }

    private static EventHandler<T> GetBackgroundThreadExecutionStrategy<T>(EventHandler<T> action)
    {
        return (sender, e) => Task.Factory.StartNew(() => action(sender, e));
    }

    private static EventHandler GetBackgroundThreadExecutionStrategy(EventHandler handler)
    {
        return (sender, e) => Task.Factory.StartNew(() => handler(sender, e));
    }

    private static EventHandler<T> GetCurrentThreadExecutionStrategy<T>(EventHandler<T> action)
    {
        var currentSynchronizationContext = SynchronizationContext.Current;

        return (sender, e) => PostToSynchronizationContext(currentSynchronizationContext, () => action(sender, e));
    }

    private static EventHandler GetCurrentThreadExecutionStrategy(EventHandler handler)
    {
        var currentSynchronizationContext = SynchronizationContext.Current;

        return (sender, e) => PostToSynchronizationContext(currentSynchronizationContext, () => handler(sender, e));
    }

    private static void PostToSynchronizationContext(SynchronizationContext synchronizationContext, Action action)
    {
        try
        {
            synchronizationContext.Post(state => action(), null);
        }
        catch (Exception ex)
        {
            if (!ex.Message.StartsWith("The operation cannot be completed because the window is being closed", StringComparison.Ordinal))
            {
                throw;
            }
        }
    }
}
查看更多
登录 后发表回答