Переносимая библиотека классов, эквивалентная Dispatcher.Invoke или Dispatcher.RunAsync

В .NET, Windows 8 и Windows Phone 7 у меня есть код, похожий на этот:

public static void InvokeIfRequired(this Dispatcher dispatcher, Action action)
{
    if (dispatcher.CheckAccess())
    {
        action();
    }
    else
    {
        dispatcher.Invoke(action);
    }
}

Как мне что-то сделать в переносимой библиотеке классов? Было бы неплохо иметь одну независимую от платформы реализацию этого. Моя идея состоит в том, чтобы использовать TPL, который недоступен в WP7, но определенно скоро будет.

// PortableDispatcher must be created on the UI thread and then made accessible 
// maybe as a property in my ViewModel base class.
public class PortableDispatcher
{
    private TaskScheduler taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();

    public void Invoke(Action action)
    {
        if (Alread on UI thread. How would I do this.)
        {
            action();
        }

        Task.Factory.StartNew(
            action, 
            CancellationToken.None,
            TaskCreationOptions.None,
            taskScheduler);
    }
}

Единственное, в чем я не уверен, так это в том, каковы будут последствия этого для производительности. Возможно, я сделаю некоторые тесты.


person Muhammad Rehan Saeed    schedule 29.06.2012    source источник


Ответы (2)


Вы можете использовать метод SynchronizationContext.Post или Send. . Он переносим, ​​и когда вы используете инфраструктуру пользовательского интерфейса с диспетчером, текущий контекст синхронизации делегирует работу диспетчеру.

В частности, вы можете использовать следующий код:

void InvokeIfRequired(this SynchroniationContext context, Action action)
{
    if (SynchroniationContext.Current == context)
    {
        action();
    }
    else
    {
        context.Send(action) // send = synchronously
        // context.Post(action)  - post is asynchronous.
    }  
}
person Alex Shtof    schedule 29.06.2012
comment
Хороший. Как бы вы создали метод расширения, который я использовал выше для диспетчера. В частности, как вы проверяете, работаете ли вы уже в потоке пользовательского интерфейса вместо использования dispatcher.CheckAccess(). - person Muhammad Rehan Saeed; 29.06.2012
comment
Также является Post the Equivelant BeginInvoke и Send эквивалентом Invoke? - person Muhammad Rehan Saeed; 29.06.2012
comment
Не нужно проверять перед публикацией. Post уже выполняет надлежащие проверки. - person Panagiotis Kanavos; 29.06.2012
comment
Не похоже. Отражатель показывает это: public virtual void Post(SendOrPostCallback d, object state) { ThreadPool.QueueUserWorkItem(new WaitCallback(d.Invoke), state); } - person Muhammad Rehan Saeed; 29.06.2012
comment
Обратите внимание, что SynchronizationContext.Send не поддерживается в приложениях в стиле Metro — вы получите исключение NotSupportedException. Из-за этого метод теперь помечен как устаревший в переносимых библиотеках классов. См. msdn.microsoft. com/ru-ru/library/ - person Daniel Plaisted; 02.07.2012
comment
@DanielPlaisted Означает ли это, что невозможно обрабатывать диспетчера в переносимом классе (при поддержке приложений в стиле Metro)? Интересно, что в переносимой библиотеке классов для PCL написано «Устарело» (предупреждение компилятора), но поддерживается в: Windows 8 для .NET для приложений Магазина Windows. Является ли POST хорошей альтернативой (асинхронной). - person tofutim; 21.11.2012
comment
context.Post запрашивает SendOrPostCallback - person tofutim; 21.11.2012

С появлением ТПЛ. Я придумал немного улучшенную версию принятого ответа, которая возвращает задачу и может ожидаться с использованием новых ключевых слов async и await.

public Task RunAsync(Action action)
{
    TaskCompletionSource<object> taskCompletionSource = new TaskCompletionSource<object>();

    if (this.synchronizationContext == SynchronizationContext.Current)
    {
        try
        {
            action();
            taskCompletionSource.SetResult(null);
        }
        catch (Exception exception)
        {
            taskCompletionSource.SetException(exception);
        }
    }
    else
    {
        // Run the action asyncronously. The Send method can be used to run syncronously.
        this.synchronizationContext.Post(
            (obj) => 
            {
                try
                {
                    action();
                    taskCompletionSource.SetResult(null);
                }
                catch (Exception exception)
                {
                    taskCompletionSource.SetException(exception);
                }
            }, 
            null);
    }

    return taskCompletionSource.Task;
}
person Muhammad Rehan Saeed    schedule 07.10.2013
comment
Ницца. Похоже, вы создали класс, но только опубликовали метод, поэтому (возможно, очевидное) примечание для других. this.synchronizationContext должен быть назначен в потоке, в котором вы хотите, чтобы действие выполнялось перед вызовом метода. На самом деле я немного изменил выше и создал расширение: public Task RunAsync (этот контекст SynchronizationContext, действие Action). Теперь это больше похоже на оригинальный ответ, поскольку он может стоять отдельно. Лучший из обоих ответов. :) - person Wes; 08.04.2014