Silverlight 5.0 pomoci Async Targeting Pack podporuje použití nové syntaxe resp. nových klíčových slov async/await. Pokud ale do Silverlight projektu přidáme referenci na WCF službu není zde podporováno automatické “zaobalení“ asynchronních volání operací WCF služby do metod vracející objekt Task. Toto si musíme udělat sami.
Dialog pro přidání nebo konfiguraci reference na WCF službu vypadá totiž v Silverlightu (a VS 2012) následovně:
Je vidět, že volbu “Generate task-based operations” zde nelze vybrat a pro generování proxy tříd je tedy stále použit event-based asynchronous pattern(EAP) (i když alternativně lze použít i APM).
Pokud tedy máme na WCF službě implementovanou například tuto operaci:
[OperationContract]
public UserInfo GetUserInfo(string sessionToken);
Je pro ní na klientské straně ve WCF proxy dle EAP vygenerováno následující:
public partial class GetUserInfoCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs
{
public UserInfo Result { get; }
}
public event EventHandler<GetUserInfoCompletedEventArgs> GetUserInfoCompleted;
public void GetUserInfoAsync(string sessionToken);
public void GetUserInfoAsync(string sessionToken, object userState);
Co my musíme udělat je to, že pro každou operaci implementujeme metodu, která mechanicky “zaobalí” volání asynchronní operace jako Task (nebo Task<T>). Přitom použijeme objekt TaskCompletionSource<TResult>. Metodu umístíme do statické třídy Proxy.
Pro operaci výše to bude vypadat takto:
public static Task<UserInfo> GetUserInfo(string sessionToken, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
{
var tcs = new TaskCompletionSource<UserInfo>();
if (cancellationToken.IsCancellationRequested)
{
tcs.SetCanceled();
return tcs.Task;
}
var cancellationRegistration = cancellationToken.Register(() => tcs.TrySetCanceled());
var service = Proxy.Service;
Guid token = Guid.NewGuid();
EventHandler<GetUserInfoCompletedEventArgs> handler = null;
handler = (s, e) =>
{
if ((Guid)e.UserState != token)
{
return;
}
service.GetUserInfoCompleted -= handler;
cancellationRegistration.Dispose();
if (e.Cancelled || cancellationToken.IsCancellationRequested)
{
tcs.TrySetCanceled();
}
else if (e.Error != null)
{
tcs.SetException(e.Error);
}
else
{
tcs.SetResult(e.Result);
}
};
service.GetUserInfoCompleted += handler;
service.GetUserInfoAsync(sessionToken, token);
return tcs.Task;
}
Instance WCF proxy je zde zpřístupněna přes vlastnost Proxy.Service. Její implementace může vypadat například následovně:
private static WCFServiceClient s_Service;
private static WCFServiceClient Service
{
get
{
if (s_Service == null)
{
//Inicializace proxy služby
var address = new Uri(Application.Current.Host.Source, "../WCFService.svc");
var endpoint = new System.ServiceModel.EndpointAddress(address);
var binding = new CustomBinding(new BinaryMessageEncodingBindingElement(),
address.Scheme == "https" ? new HttpsTransportBindingElement() { MaxReceivedMessageSize = 2147483647 } : new HttpTransportBindingElement() { MaxReceivedMessageSize = 2147483647 });
s_Service = new WCFServiceClient(binding, endpoint);
}
return s_Service;
}
}
Ještě bych se chvilku pozastavil na tom, jak se řešena podpora pro cancellation. Metodě GetUserInfo může být při volání předán CancellationToken, který umožňuje signalizovat požadavek na zrušení probíhající operace.
V takovém případě ale není prováděna žádná komunikace pro přerušení operace směrem na server, protože taková obsluha není u obecné operace možná. Místo toho je při signalizování zrušení operace alespoň provedeno okamžité přerušení čekání (pomoci await) na její dokončení. Tohoto je docíleno v nastaveném callbacku metodou CancellationToken.Register. Při dokončení operace na serveru je pak případ jejího případného zrušení již pouze ošetřen tak, aby nebyla provedena žádná další akce.