Utilizzando Rx per semplificare un Silverlight richiesta di servizio Web asincrono
-
25-09-2019 - |
Domanda
Ho scritto una libreria client Silverlight semplificato per il mio servizio web WCF usando Rx, però ho notato a volte mi manca eventi terminati.
public IObservable<XElement> GetReport(string reportName)
{
return from client in Observable.Return(new WebServiceClient())
from request in Observable.ToAsync<string>(client.GetReportDataAsync)(reportName)
from result in Observable.FromEvent<GetReportDataCompletedEventArgs>(client, "GetReportDataCompleted").Take(1)
from close in this.CloseClient(client)
select result.EventArgs.Result;
}
Credo che il problema è causato dal fatto che il servizio Web si chiama e torna prima di sottoscrivere l'evento completato. Non riesco a capire come ottenere Rx di sottoscrivere l'evento prima della chiamata asincrona. Ho provato StartWith ma che richiede che i tipi di input e output essere la stessa, tutte le idee?
Soluzione
Sembra che la risposta migliore è quella di utilizzare Observable.CreateWithDisposable ()
per es.
public IObservable<XElement> GetReport(string reportName)
{
return from client in Observable.Return(new WebServiceClient())
from completed in Observable.CreateWithDisposable<GetReportDataCompletedEventArgs>(observer =>
{
var subscription = Observable.FromEvent<GetReportDataCompletedEventArgs>(client, "GetReportDataCompleted")
.Take(1)
.Select(e => e.EventArgs)
.Subscribe(observer);
client.GetReportDataAsync(reportName);
return subscription;
})
from close in this.CloseClient(client)
select completed.Result;
}
Per rendere questo più facile lavorare con I refactoring il CreateWithDisposable in una funzione comune che può essere utilizzato con tutte le mie chiamate di servizio web, tra cui determinando automaticamente il nome dell'evento dal tipo args evento:
private IObservable<T> CallService<T>(ICommunicationObject serviceClient, Action start) where T : AsyncCompletedEventArgs
{
if (typeof(T) == typeof(AsyncCompletedEventArgs))
{
throw new InvalidOperationException("Event arguments type cannot be used to determine event name, use event name overload instead.");
}
string completedEventName = typeof(T).Name.TrimEnd("EventArgs");
return CallService<T>(serviceClient, start, completedEventName);
}
private IObservable<T> CallService<T>(ICommunicationObject serviceClient, Action start, string completedEventName) where T : AsyncCompletedEventArgs
{
return Observable.CreateWithDisposable<T>(observer =>
{
var subscription = Observable.FromEvent<T>(serviceClient, completedEventName).Take(1).Select(e => e.EventArgs).Subscribe(observer);
start();
return subscription;
});
}
// Example usage:
public IObservable<XElement> GetReport(string reportName)
{
return from client in Observable.Return(new WebServiceClient())
from completed in this.CallService<GetReportDataCompletedEventArgs>(client, () => client.GetReportDataAsync(reportName))
from close in this.CloseClient(client)
select completed.Result;
}
/// <summary>
/// Asynchronously closes the web service client
/// </summary>
/// <param name="client">The web service client to be closed.</param>
/// <returns>Returns a cold observable sequence of a single success Unit.</returns>
private IObservable<AsyncCompletedEventArgs> CloseClient(WebServiceClient client)
{
return this.CallService<AsyncCompletedEventArgs>(client, client.CloseAsync, "CloseCompleted");
}
Spero che questo aiuti qualcun altro!
Altri suggerimenti
Ho bisogno di utilizzare WebClient.DownloadStringAsync
generale ecco la mia versione.
In primo luogo, avvolgere l'evento:
public static IObservable<IEvent<DownloadStringCompletedEventArgs>>
GetDownloadStringObservableEvent(this WebClient wc)
{
return Observable.FromEvent<DownloadStringCompletedEventArgs>(
wc, "DownloadStringCompleted");
}
Quindi creare il metodo di estensione:
public static IObservable<string> GetDownloadString(this WebClient wc, Uri uri)
{
return Observable.CreateWithDisposable<string>(
observer => {
// Several downloads may be going on simultaneously. The token allows
// us to establish that we're retrieving the right one.
Guid token = Guid.NewGuid();
var stringDownloaded = wc.GetDownloadStringObservableEvent()
.Where(evt => ((Guid)evt.EventArgs.UserState) == token)
.Take(1); //implicitly unhooks handler after event is received
bool errorOccurred = false;
IDisposable unsubscribe =
stringDownloaded.Subscribe(
// OnNext action
ev => {
// Propagate the exception if one is reported.
if (ev.EventArgs.Error != null) {
errorOccurred = true;
observer.OnError(ev.EventArgs.Error);
} else if (!ev.EventArgs.Cancelled) {
observer.OnNext(ev.EventArgs.Result);
}
},
// OnError action (propagate exception)
ex => observer.OnError(ex),
// OnCompleted action
() => {
if (!errorOccurred) {
observer.OnCompleted();
}
});
try {
wc.DownloadStringAsync(uri, token);
} catch (Exception ex) {
observer.OnError(ex);
}
return unsubscribe;
}
);
}
L'uso è semplice:
wc.GetDownloadString(new Uri("http://myservice"))
.Subscribe(resultCallback , errorCallback);