Возможное решение проблемы с несколькими диспетчерами пользовательского интерфейса в одном методе?

У меня возникла проблема с использованием нескольких диспетчеров пользовательского интерфейса для изменения списка, привязанного к пользовательскому интерфейсу. Метод с выходом только при попадании в первый диспетчер. Если я оберну весь метод в диспетчер, он сработает, но у меня есть другое решение, но я не уверен, что оно подходит:

По сути, у меня есть сокет, который в бесконечном цикле прослушивает сетевые команды с мультимедийного устройства. Когда он находит его, он вызывает ProcessCommand.

Эта функция вызывает один из 50+ методов для обработки определенных команд. Эти функции хранят внутреннее состояние, но в основном вызывают события, на которые может подписаться мое основное приложение, поэтому оно знает, когда что-то вроде изменения громкости, и я могу обновить пользовательский интерфейс.

Они работают довольно хорошо, за исключением одного случая, отмеченного в начале, когда мне нужно изменить объект состояния, который связан с использованием нескольких диспетчеров в одном и том же методе, и он не работает, и кажется, что работает перенос метода в один большой диспетчер.

Другое решение, которое я нашел, - запустить ProcessCommand, работающий в фоновом прослушивателе сокета в диспетчере пользовательского интерфейса, то есть:

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

Затем это будет просачиваться во все 50+ отдельных методов ProcessCommandXYZ, а также в мое основное приложение, где я подписываюсь на эти события на нескольких страницах. В настоящее время мне приходится использовать диспетчер пользовательского интерфейса на каждом из них, поэтому что-то вроде:

Фоновая задача прослушивателя сокетов > {DispatcherUI ProcessCommand} > ProcessVolumeCommand > Raise OnVolumeChangedEvent > Пользовательский интерфейс обновлений подписчика

Сделал бы это так, чтобы мне не нужно было помещать каждого подписчика событий в диспетчер, но, что наиболее важно, устраняет проблему, с которой я сталкиваюсь с несколькими диспетчерами в одном методе:

Правильное использование Dispatcher в случае обновления пользовательского интерфейса

Это кажется хорошим решением?


person Jason    schedule 10.09.2015    source источник
comment
Потенциальная проблема с этим дизайном заключается в том, что теперь вся обработка выполняется в потоке пользовательского интерфейса. Если обработка стоит дешево и происходит не слишком часто, это не проблема, но если она дорогая или выполняется много раз в секунду, ваш пользовательский интерфейс начнет отставать.   -  person Peter Torr - MSFT    schedule 10.09.2015


Ответы (1)


Вот мое решение для таких ситуаций, когда вы подписались на событие, которое возникает в потоке Non-ui.

//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();

И вот реализация:

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;
            }
        }
    }
}
person RavingDev    schedule 10.09.2015
comment
События, на которые я подписываюсь в своем основном приложении, такие как громкость, представляют собой не столько проблему, сколько несколько функций в классе низкоуровневых коммуникаций и командных процессов, которым нужен диспетчер, потому что мое основное приложение привязывается непосредственно к одному из списков. - person Jason; 10.09.2015
comment
@ Джейсон, о каких списках ты говоришь? это ObservableCollection или что? - person RavingDev; 10.09.2015
comment
Да, медиаплеер может отправлять меню, я храню его в наблюдаемой коллекции и привязываю к списку в своем основном приложении. Мне приходится много манипулировать функцией, формирующей меню, поэтому несколько диспетчеров не работают там, где работает один, и натолкнули меня на мысль просто поместить диспетчер на более низкий уровень. Я поместил основной код для функции меню в stackoverflow.com/questions/32489503/ - person Jason; 10.09.2015