2009-12-14 9 views
15

Sto scrivendo il refactoring di un programma Silverlight per consumare una parte della sua logica aziendale esistente da un servizio WCF. In tal modo, ho eseguito la restrizione in Silverlight 3 che consente solo le chiamate asincrone ai servizi WCF per evitare casi in cui le chiamate di servizio di lunga durata o non reattive bloccano il thread dell'interfaccia utente (SL ha un modello di accodamento interessante per richiamare i servizi WCF sul thread dell'interfaccia utente).Insidie ​​di (Mis) utilizzo di Iteratori C# per implementare le Coroutine

Di conseguenza, scrivere ciò che una volta era semplice, sta diventando rapidamente più complesso (vedere gli esempi di codice alla fine della mia domanda).

Idealmente, vorrei usare coroutines per semplificare l'implementazione, ma purtroppo C# non supporta attualmente le coroutine come una funzione di lingua nativa. Tuttavia, C# ha il concetto di generatori (iteratori) usando la sintassi yield return. La mia idea è di ri-utilizzare la parola chiave yield per permettermi di costruire un modello di coroutine semplice con la stessa logica.

Sono riluttante a farlo, tuttavia, perché sono preoccupato che potrebbero esserci delle insidie ​​(tecniche) nascoste che non sto anticipando (data la mia relativa inesperienza con Silverlight e WCF). Sono anche preoccupato che il meccanismo di implementazione possa non essere chiaro agli sviluppatori futuri e potrebbe ostacolare piuttosto che semplificare i loro sforzi per mantenere o estendere il codice in futuro. Ho visto questa domanda su SO riguardo la riproposizione degli iteratori per costruire macchine a stati: implementing a state machine using the "yield" keyword, e anche se non è esattamente la stessa cosa che sto facendo, mi fa mettere in pausa.

Tuttavia, ho bisogno di fare qualcosa per nascondere la complessità delle chiamate di servizio e gestire lo sforzo e il potenziale rischio di difetti in questo tipo di cambiamento. Sono aperto ad altre idee o approcci che posso utilizzare per risolvere questo problema.

La versione originale non WCF del codice sembra qualcosa di simile:

void Button_Clicked(object sender, EventArgs e) { 
    using(var bizLogic = new BusinessLogicLayer()) { 
     try { 
      var resultFoo = bizLogic.Foo(); 
      // ... do something with resultFoo and the UI 
      var resultBar = bizLogic.Bar(resultFoo); 
      // ... do something with resultBar and the UI 
      var resultBaz = bizLogic.Baz(resultBar); 
      // ... do something with resultFoo, resultBar, resultBaz 
     } 
    } 
} 

La versione re-factoring WCF diventa un po 'più coinvolti (anche senza la gestione delle eccezioni e pre/post condizioni di prova):

// fields needed to manage distributed/async state 
private FooResponse m_ResultFoo; 
private BarResponse m_ResultBar; 
private BazResponse m_ResultBaz; 
private SomeServiceClient m_Service; 

void Button_Clicked(object sender, EventArgs e) { 
    this.IsEnabled = false; // disable the UI while processing async WECF call chain 
    m_Service = new SomeServiceClient(); 
    m_Service.FooCompleted += OnFooCompleted; 
    m_Service.BeginFoo(); 
} 

// called asynchronously by SL when service responds 
void OnFooCompleted(FooResponse fr) { 
    m_ResultFoo = fr.Response; 
    // do some UI processing with resultFoo 
    m_Service.BarCompleted += OnBarCompleted; 
    m_Service.BeginBar(); 
} 

void OnBarCompleted(BarResponse br) { 
    m_ResultBar = br.Response; 
    // do some processing with resultBar 
    m_Service.BazCompleted += OnBazCompleted; 
    m_Service.BeginBaz(); 
} 

void OnBazCompleted(BazResponse bz) { 
    m_ResultBaz = bz.Response; 
    // ... do some processing with Foo/Bar/Baz results 
    m_Service.Dispose(); 
} 

Il codice sopra è ovviamente una semplificazione, in quanto omette la gestione delle eccezioni, controlli nullità, e altre pratiche che sarebbero necessarie in codice di produzione. Ciò nonostante, penso che dimostri il rapido aumento della complessità che inizia a verificarsi con il modello di programmazione WCF asincrono in Silverlight. Il ri-factoring dell'implementazione originale (che non utilizzava un livello di servizio, ma aveva la sua logica incorporata nel client SL) sta rapidamente cercando di essere un compito scoraggiante. E uno che rischia di essere abbastanza incline agli errori.

La versione co-ordinaria del codice sarebbe simile a questa (non ho ancora testato questa):

void Button_Clicked(object sender, EventArgs e) { 
    PerformSteps(ButtonClickCoRoutine); 
} 

private IEnumerable<Action> ButtonClickCoRoutine() { 
    using(var service = new SomeServiceClient()) { 
     FooResponse resultFoo; 
     BarResponse resultBar; 
     BazResponse resultBaz; 

     yield return() => { 
      service.FooCompleted = r => NextStep(r, out resultFoo); 
      service.BeginFoo(); 
     }; 
     yield return() => { 
      // do some UI stuff with resultFoo 
      service.BarCompleted = r => NextStep(r, out resultBar); 
      service.BeginBar(); 
     }; 
     yield return() => { 
      // do some UI stuff with resultBar 
      service.BazCompleted = r => NextStep(r, out resultBaz); 
      service.BeginBaz(); 
     }; 
     yield return() => { 
      // do some processing with resultFoo, resultBar, resultBaz 
     } 
    } 
} 

private void NextStep<T>(T result, out T store) { 
    store = result; 
    PerformSteps(); // continues iterating steps 
} 

private IEnumerable<Action> m_StepsToPerform; 
private void PerformSteps(IEnumerable<Action> steps) { 
    m_StepsToPerform = steps; 
    PerformSteps();   
} 

private void PerformSteps() { 
    if(m_StepsToPerform == null) 
     return; // nothing to do 

    m_StepsToPerform.MoveNext(); 
    var nextStep = m_StepsToPerform.Current; 
    if(nextStep == null) { 
     m_StepsToPerform.Dispose(); 
     m_StepsToPerform = null; 
     return; // end of steps 
    } 
    nextStep(); 
} 

Ci sono un sacco di cose che devono essere migliorate nel codice qui sopra. Ma la premessa di base è quella di calcolare il pattern di continuazione (creando un punto di intercettazione per la gestione delle eccezioni e vari controlli) consentendo nel contempo il modello asincrono basato su evento di WCF da guidare quando ogni passo viene eseguito, in pratica quando l'ultima chiamata WCF asincronica completa. Mentre in superficie questo sembra più codice, vale la pena ricordare che PerformSteps() e NextStep() sono riutilizzabili, solo l'implementazione in ButtonClickCoRoutine() cambierebbe con ogni sito di implementazione diverso.

Non sono del tutto sicuro che mi piaccia questo modello, e non sarei sorpreso se esistesse un modo più semplice per implementarlo.Ma non sono stato in grado di trovarne uno su "interwebs" o MSDN o altrove. Grazie in anticipo per l'aiuto.

+7

No buona risposta, se non per dire che ci sentiamo il vostro dolore. Stiamo facendo molte ricerche su come le persone usano l'asincronia su Silverlight e altri quadri. I tipi di trasformazioni che facciamo per rendere gli iteratori simili a coroutine economiche sono un inizio, ma non hanno una generalità sufficiente per risolvere tutti i problemi in questo spazio. Mi piacerebbe sicuramente vedere questo tipo di schema espresso in modo pulito nella lingua, nella stessa maniera in cui esprimiamo in modo pulito i lati generazione (rendimento) e consumo (foreach) del pattern iteratore. –

+0

Immagino che questa domanda spieghi la motivazione per l'aggiunta di asincrona/attesa a C#. –

risposta

11

Si dovrebbe assolutamente guardare il Concurrency and Coordination Runtime. Usa gli iteratori proprio per questo scopo.

D'altra parte, si dovrebbe anche guardare Parallel Extensions e il suo approccio alle continuazioni. Parallel Extensions fa parte di .NET 4.0, mentre il CCR richiede licenze separate. Io vorremmo consigliarti di andare con un quadro scritto da persone che mangiano, respirano e dormono questa roba però. È troppo facile ottenere dettagli sbagliati da soli.

+0

Sono d'accordo. La mia comprensione era che il CCR aveva alcune restrizioni di licenza (che se le capisco correttamente mi renderanno impossibile l'uso). La libreria di Parallel Extensions e .NET 4.0 sono attualmente in anteprima - li ho guardati ma non vedo alcuna classe o struttura per fare questo genere di cose. C'era una parte particolare della PTE che avevi in ​​mente? – LBushkin

+4

LBushkin: puoi ottenere il backport delle estensioni parallele come parte delle estensioni reattive (vedi il mio post per il collegamento). Esiste un metodo Task.FromAsync (+ overload) che consente di costruire un'attività da una coppia di metodi basata su IAsyncResult, oltre a utilizzare Task.ContinueWith per le continuazioni. –

+0

Reed: Grazie. Daro un'occhiata a quello. – LBushkin

4

Il Reactive Extensions for .NET fornisce un modello molto più pulito per la gestione di questo.

Forniscono estensioni che consentono di scrivere delegati semplici contro eventi asincroni in modo molto, molto più pulito. Consiglio di esaminarli e adattarli a questa situazione.

+0

. Ho guardato Rx.NET, tuttavia è in anteprima e probabilmente non sarà disponibile per i miei tempi. Sono anche poco chiaro su come scrivere esattamente questo tipo di codice RX.NET - Ho visto esempi che illustrano il trattamento di sorgenti di eventi come IObservable <> - iteratori essenzialmente infiniti, ma non su come strutturare questo pattern. Sei a conoscenza di esempi che potrei guardare? – LBushkin

+0

Dai un'occhiata a questo: http://themechanicalbride.blogspot.com/2009/07/developing-with-rx-part-2-converting.html Richiede una piccola libreria di wrapping, ma sarebbe facile estenderla con continuazioni. Esistono, tuttavia, alcuni esempi di attività da osservabili con Rx. –

+0

+1 RX è la direzione per prendere questo genere di cose in Silverlight. Non dovrei preoccuparmi troppo dello stato di "Anteprima" di Rx, se fosse davvero una preoccupazione, forse Silverlight nel suo complesso non fa per voi. – AnthonyWJones

1

Non ho letto tutto.

Utilizzano questa strategia nello studio di robotica CCR e una serie di altri progetti utilizzano questa strategia. Un'alternativa è usare LINQ, vedi ad es. this blog per una descrizione. The Reactive framework (Rx) è un tipo costruito su queste linee.

Luca cita nel suo PDC talk che forse la versione futura di C#/VB potrebbe aggiungere primitive asincrone al linguaggio.

Nel frattempo, se è possibile utilizzare F #, è una strategia vincente. In questo momento, quello che puoi fare con F # qui soffia tutto fuori dall'acqua.

EDIT

Per citare l'esempio dal mio blog, si supponga di avere un client WCF che si desidera chiamare un paio di metodi su. La versione sincrona potrebbe essere scritto come

// a sample client function that runs synchronously 
let SumSquares (client : IMyClientContract) = 
    (box client :?> IClientChannel).Open() 
    let sq1 = client.Square(3) 
    let sq2 = client.Square(4) 
    (box client :?> IClientChannel).Close() 
    sq1 + sq2 

e il codice asincrono corrispondente sarebbe

// async version of our sample client - does not hold threads 
// while calling out to network 
let SumSquaresAsync (client : IMyClientContract) = 
    async { do! (box client :?> IClientChannel).OpenAsync() 
      let! sq1 = client.SquareAsync(3) 
      let! sq2 = client.SquareAsync(4) 
      do! (box client :?> IClientChannel).CloseAsync() 
      return sq1 + sq2 } 

Nessun callback folli, è possibile utilizzare costrutti di controllo, come if-then-else, mentre, try-, infine, ecc., scrivilo quasi esattamente come scrivi codice in linea retta, e tutto funziona, ma ora è asincrono. È molto facile prendere una data coppia di metodi BeginFoo/EndFoo e creare i corrispondenti metodi asincroni F # da usare in questo modello.

+0

Come ho risposto a Reed e Jon, ho esaminato CCR, Parallel Extensions e Reactive.NET - tuttavia non ho visto un buon esempio che possa riguardare ciò che sto facendo. Ciò mi aiuterebbe molto a decidere quali (se esistono) di queste librerie potrebbero adattarsi al conto. – LBushkin

+0

Hai un esempio di F # che potrei guardare? Non penso di poter utilizzare F # in un'applicazione Silverlight 3, ma potrei essere in grado di imparare qualcosa dall'approccio che usa. – LBushkin

+0

Scopri Luca's PDC talk dal 2008 qui: http://channel9.msdn.com/pdc2008/TL11/ Nel video, guarda circa 10 minuti a partire da 50 minuti, mostra come è facile effettuare chiamate asincrone in F #. È possibile utilizzare una libreria F # in un'applicazione Silverlight 3. – Brian

0

Si potrebbe anche prendere in considerazione l'AsyncEnumerator di Jeffrey Richter, che fa parte della sua libreria "power threading". Ha lavorato insieme al team CCR per sviluppare il CCR. L'AsyncEnumerator secondo Jeffrey è più "leggero" rispetto al CCR. Personalmente ho giocato con AsyncEnumerator ma non con il CCR.

Tuttavia non l'ho usato per la rabbia - finora ho trovato i limiti con l'utilizzo degli enumeratori per implementare le coroutine troppo dolorose. Attualmente sto imparando F # a causa, tra l'altro, dei flussi di lavoro asincroni (se ricordo bene il nome) che sembrano coroutine o "continuazioni" (dimentico il nome corretto o le distinzioni esatte tra i termini).

In ogni caso, ecco alcuni link:

http://www.wintellect.com/PowerThreading.aspx

Channel 9 video on AsyncEnumerator

MSDN Article

Problemi correlati