W jaki sposób wywołać metodę usługi sieciowej w sposób asynchroniczny, aby skorzystać z async-await?
W jaki sposób wywołać metodę usługi sieciowej w sposób asynchroniczny, aby skorzystać z async-await?
W przypadku usługi WCF Services jest to dosyć proste, bo wystarczy zaznaczyć opcję Generate task-based Operations przy dodawaniu odwołania do usługi za pomocą Add Service Reference:
Po zaznaczeniu tej opcji, generator klasy proxy wygeneruje metody z użyciem klasy Task i będzie je można wywołać asynchronicznie za pomocą async-await.
W przypadku "starych" usług XML Web Services nie ma takiej opcji. Generatora proxy do XML Web Services generuje co prawda metody asynchroniczne, ale oparte o Event-based Asynchronous Pattern (w skrócie EAP).
Wzorzec EAP opiera się na parze:
- [Operation]Async - metoda asynchroniczna
- [Operation]Completed - zdarzenie wywoływane po zakończeniu metody asynchronicznej
Więcej na temat EAP można znaleźć w msdn.
Operacje async-await współpracują natomiast ze wzorcem Task Event Pattern (w skrócie TAP).
Na szczęście istnieje sposób na przekształcenie EAP w TAP. Jednak tworzenie dla każdej metody usługi sieciowej takiej konwersji jest bardzo pracochłonne, zwłaszcza jeśli tych operacji jest sporo.
Dlatego utworzyłem prosty szablon, który umożliwia szybkie i łatwe opakowanie metod w async-await:
private static TaskCompletionSource<T> CreateSource<T>(object state) { return new TaskCompletionSource<T>( state, TaskCreationOptions.None); } private static void TransferCompletion<T>( TaskCompletionSource<T> tcs, T e, Func<T> getResult, Action unregisterHandler) where T : AsyncCompletedEventArgs { if (e.Error != null) { tcs.TrySetException(e.Error); } else if (e.Cancelled) { tcs.TrySetCanceled(); } else { tcs.TrySetResult(getResult()); } if (unregisterHandler != null) unregisterHandler(); }
Zwracam uwagę na wykorzystanie generic'ów.
Teraz można z niego skorzystać. Oto przykład:
public Task<LoginCompletedEventArgs> LoginAsyncTask(string userName, string password) { var tcs = CreateSource<LoginCompletedEventArgs>(null); client.LoginCompleted += (sender, e) => TransferCompletion(tcs, e, () => e, null); client.LoginAsync(userName, password); return tcs.Task; }
W podobny sposób opakowujemy wszystkie metody usługi. Co prawda nie jest to pełny automat, bo wymaga od programisty pokrycia metod, ale i tak pozwala zaoszczędzić sporo kodu i czasu.
Może w przyszłości napiszę pełny automat z wykorzystaniem Roslyn. :)