2014-09-30 5 views
0

У меня такое ощущение, что должно быть лучшее решение, чем то, что я придумал; вот в чем проблема:WPF/WCF Асинхронный сервис Call and SynchronizationContext

Форма WPF вызовет метод WCF, который возвращает bool. Сам вызов не должен находиться в потоке пользовательского интерфейса, и результат вызова должен отображаться в форме, поэтому возврат должен быть привязан к потоку пользовательского интерфейса.

В этом примере я создал класс «ServiceGateway», которому форма передаст метод, который будет выполнен после завершения операции входа. Шлюз должен вызывать этот делегат, заполняющий логин, с использованием интерфейса синхронизации UI, который передается при создании шлюза из формы. Метод Login вызывает вызов _proxy.Login с использованием anon. асинхронному делегат, а затем предоставляет функцию обратного вызова, который вызывает делегату («обратного вызова» параметры), приведенные к шлюзу (из формы) с помощью пользовательского интерфейса SynchronizationContext:

[CallbackBehavior(UseSynchronizationContext = false)] 
public class ChatServiceGateway : MessagingServiceCallback 
{ 

    private MessagingServiceClient _proxy; 
    private SynchronizationContext _uiSyncContext; 

    public ChatServiceGateway(SynchronizationContext UISyncContext) 
    { 
     _proxy = new MessagingServiceClient(new InstanceContext(this)); 
     _proxy.Open(); 
     _uiSyncContext = UISyncContext; 
    } 


    public void Login(String UserName, Action<bool> callback) 
    { 
     new Func<bool>(() => _proxy.Login(UserName)).BeginInvoke(delegate(IAsyncResult result) 
     { 
      bool LoginResult = ((Func<bool>)((AsyncResult)result).AsyncDelegate).EndInvoke(result); 
      _uiSyncContext.Send(new SendOrPostCallback(obj => callback(LoginResult)), null); 
     }, null); 

    } 

Метод Логина вызывается из формы в ответ на событие нажатия кнопки.

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

Я хотел бы сохранить асинхронное поведение и синхронизацию ui, инкапсулированную в шлюз. Было бы лучше иметь асинхронное поведение, реализованное на стороне WCF? В принципе, меня интересует, могу ли я реализовать вышеприведенный код более универсально для других методов или если все будет лучше.

+1

Вы должны иметь возможность повторно создать клиент WCF с асинхронными API-интерфейсами на основе 'Task', что значительно облегчит их очистку. –

ответ

1

При условии, что вы ориентируетесь как минимум на VS 2012 и .NET 4.5, async/await - это путь. Обратите внимание на отсутствие ссылки SynchronizationContext - она ​​снимается под обложками до await и отправляется обратно до завершения операции async.

public async Task Login(string userName, Action<bool> callback) 
{ 
    // The delegate passed to `Task.Run` is executed on a ThreadPool thread. 
    bool loginResult = await Task.Run(() => _proxy.Login(userName)); 

    // OR 
    // await _proxy.LoginAsync(UserName); 
    // if you have an async WCF contract. 

    // The callback is executed on the thread which called Login. 
    callback(loginResult); 
} 

Task.Run в основном используется для передачи CPU переплета работы в пул потоков, поэтому в приведенном выше примере это злоупотреблять несколько, но если вы не хотите, чтобы переписать контракт, реализованный MessagingServiceClient использовать асинхронный Task - основанные методы, это все еще довольно хороший способ пойти.

Или .NET 4.0 путь (без async/await поддержки):

public Task Login(string userName, Action<bool> callback) 
{ 
    // The delegate passed to `Task.Factory.StartNew` 
    // is executed on a ThreadPool thread. 
    var task = Task.Factory.StartNew(() => _proxy.Login(userName)); 

    // The callback is executed on the thread which called Login. 
    var continuation = task.ContinueWith(
     t => callback(t.Result), 
     TaskScheduler.FromCurrentSynchronizationContext() 
    ); 

    return continuation; 
} 

Это немного отличается от того, как вы сейчас делаете вещи в том, что она не несет ответственность за вызывающего абонента чтобы Login вызывался в потоке пользовательского интерфейса, если он хочет, чтобы на нем выполнялся обратный вызов. То есть, однако, стандартная практика, когда дело доходит до async, и пока вы мог сохранить ссылку на интерфейс SynchronizationContext или TaskScheduler как часть вашего ChatServiceGateway в силы обратного вызов/продолжение выполнить на правой резьбе, он будет продуть вашу реализацию и лично (и это всего лишь мое мнение ) Я бы сказал, что это немного запах кода.

+0

Отлично, именно то, что мне было интересно. Что-то просто показалось мне совсем не таким, как я привык. Благодаря! –

+0

Добро пожаловать.Последнее, что я хотел добавить: обратите внимание на то, что Стивен Клири говорит в своем комментарии к вопросу. Если вы можете регенерировать прокси-сервер WCF с включенными методами async на основе «Task», тогда ваш класс Gateway может быть уменьшен до супер-бедной абстракции или полностью исключен. –