Contratti di codice e asincronia

Qual è il modo consigliato per aggiungere le post-condizioni ai metodi asincroni che restituiscono l’ Task ?

Ho letto il seguente suggerimento:

http://social.msdn.microsoft.com/Forums/hu-HU/async/thread/52fc521c-473e-4bb2-a666-6c97a4dd3a39

Il post suggerisce di implementare ciascun metodo come sincrono, contraendolo e quindi implementando una controparte asincrona come un semplice wrapper. Purtroppo non lo vedo come una soluzione praticabile (forse attraverso il mio equivoco):

  1. Il metodo asincrono, anche se si presume che sia un wrapper per il metodo di sincronizzazione, viene lasciato senza alcun contratto di codice reale e può quindi fare ciò che desidera.
  2. Codebase che sono impegnati in asincronia difficilmente implementeranno le controparti di sincronizzazione per tutto. Di conseguenza, l’implementazione di nuovi metodi che contengono await su altri metodi asincroni sono di conseguenza forzati ad essere asincroni. Questi metodi sono intrinsecamente asincroni e non possono essere facilmente convertiti in sincrono. Non sono semplicemente involucri.

Anche se abbiamo invalidato il secondo punto dicendo che potremmo usare .Result o .Wait() invece di await (che in realtà causerebbe il SyncContext alcuni SyncContext e che SyncContext essere riscritti comunque nel metodo asincrono), I ‘ Sono ancora convinto del primo punto.

Ci sono idee alternative, o c’è qualcosa che mi è sfuggito su contratti di codice e TPL?

L’ho fatto notare al team Async, come altri hanno fatto. Attualmente, Contratti e Async sono (quasi) reciprocamente esclusivi. Quindi, almeno alcune persone in Microsoft sono consapevoli del problema, ma non sono a conoscenza di cosa stanno pensando di fare al riguardo.

Non consiglio di scrivere metodi asincroni come wrapper per i metodi di sincronizzazione. In effetti, tenderei a fare il contrario.

Le precondizioni possono funzionare. Non l’ho provato di recente; potrebbe essere necessario un piccolo wrapper attorno al metodo asincrono che include le precondizioni.

Le postcondizioni sono praticamente rotte.

Le asserzioni e le assunzioni funzionano normalmente, ma il controllo statico è veramente limitato perché le postcondizioni sono interrotte.

Gli invarianti non hanno molto senso nel mondo Async, dove lo stato mutabile tende a mettersi in mezzo. (Async ti spinge delicatamente lontano da OOP e verso uno stile funzionale).

Si spera che in VS vNext, i Contratti verranno aggiornati con una sorta di postcondition asincrona, che consentirebbe anche al correttore statico di funzionare meglio con asserzioni in metodi asincroni.

Nel frattempo, puoi avere una finta post-condizione scrivendo un’ipotesi:

 // Synchronous version for comparison. public static string Reverse(string s) { Contract.Requires(s != null); Contract.Ensures(Contract.Result() != null); return ...; } // First wrapper takes care of preconditions (synchronously). public static Task ReverseAsync(string s) { Contract.Requires(s != null); return ReverseWithPostconditionAsync(s); } // Second wrapper takes care of postconditions (asynchronously). private static async Task ReverseWithPostconditionAsync(string s) { var result = await ReverseImplAsync(s); // Check our "postcondition" Contract.Assume(result != null); return result; } private static async Task ReverseImplAsync(string s) { return ...; } 

Alcuni usi di contratti di codice non sono possibili – ad esempio, specificando post-condizioni su membri asincroni di interfacce o classi base.

Personalmente, ho appena evitato i Contracts interamente nel mio codice Async, sperando che Microsoft lo risolva in pochi mesi.

Scritto questo, ma ho dimenticato di premere “Post” … 🙂

Al momento non c’è un supporto specializzato per questo. Il meglio che puoi fare è qualcosa del genere (non usando la parola chiave async , ma la stessa idea – è ansible che il rewriter funzioni diversamente con il CTP asincrono, non l’ho ancora provato):

 public static Task Do() { Contract.Ensures(Contract.Result>() != null); Contract.Ensures(Contract.Result>().Result > 0); return Task.Factory.StartNew(() => { Thread.Sleep(3000); return 2; }); } public static void Main(string[] args) { var x = Do(); Console.WriteLine("processing"); Console.WriteLine(x.Result); } 

Tuttavia, ciò significa che il metodo “asincrono” non verrà effettivamente restituito finché l’attività non ha terminato la valutazione, quindi “elaborazione” non verrà stampata prima che siano trascorsi 3 secondi. Questo è simile al problema con i metodi che restituiscono pigramente IEnumerable s: il Contratto deve enumerare tutti gli elementi IEnumerable per garantire che la condizione sia valida, anche se il chiamante non utilizzerà effettivamente tutti gli elementi.

È ansible aggirare il problema modificando la modalità contratti su Preconditions , ma ciò significa che non verranno effettivamente verificate le condizioni del post.

Anche il controllo statico non può connettere il Result con lambda, quindi riceverai un messaggio “Assicura non provati”. (In generale, il controllore statico non dimostra comunque nulla su lambda / delegati).

Penso che per ottenere il supporto adeguato per Attività / Attendi, il team dei Contratti di codice dovrà eseguire Attività di casi speciali per aggiungere il controllo di precondizione solo dopo l’accesso al campo Result .

Pubblicazione di una nuova risposta a questo thread precedente in quanto viene restituito da google come prima risposta alla domanda su CodeContract e Async

Contratto in modo sincero sui metodi asincroni che restituiscono l’attività funzionano correttamente e non è necessario evitarli.

Contratto standard per il metodo asincrono:

 [ContractClass(typeof(ContractClassForIFoo))] public interface IFoo { Task MethodAsync(); } [ContractClassFor(typeof(IFoo))] internal abstract class ContractClassForIFoo : IFoo { #region Implementation of IFoo public Task MethodAsync() { Contract.Ensures(Contract.Result>() != null); Contract.Ensures(Contract.Result>().Status != TaskStatus.Created); Contract.Ensures(Contract.Result() != null); throw new NotImplementedException(); } #endregion } public class Foo : IFoo { public async Task MethodAsync() { var result = await Task.FromResult(new object()); return result; } } 

Se pensate che il contratto non sembra corretto, sono d’accordo che sembra fuorviante, ma funziona. E non sembra che quel reporter del contratto costringa prematuramente la valutazione del compito.

Mentre Stephen ha sollevato alcuni dubbi, alcuni test e altri contratti nel mio caso hanno fatto correttamente le loro cose.

Codice utilizzato per i test:

 public static class ContractsAbbreviators { [ContractAbbreviator] public static void EnsureTaskIsStarted() { Contract.Ensures(Contract.Result() != null); Contract.Ensures(Contract.Result().Status != TaskStatus.Created); } } [ContractClass(typeof(ContractClassForIFoo))] public interface IFoo { Task MethodAsync(int val); } [ContractClassFor(typeof(IFoo))] internal abstract class ContractClassForIFoo : IFoo { public Task MethodAsync(int val) { Contract.Requires(val >= 0); ContractsAbbreviators.EnsureTaskIsStarted(); Contract.Ensures(Contract.Result() == val); Contract.Ensures(Contract.Result() >= 5); Contract.Ensures(Contract.Result() < 10); throw new NotImplementedException(); } } public class FooContractFailTask : IFoo { public Task MethodAsync(int val) { return new Task(() => val); // esnure raises exception // Contract.Ensures(Contract.Result().Status != TaskStatus.Created); } } public class FooContractFailTaskResult : IFoo { public async Task MethodAsync(int val) { await Task.Delay(val).ConfigureAwait(false); return val + 1; // esnure raises exception // Contract.Ensures(Contract.Result() == val); } } public class Foo : IFoo { public async Task MethodAsync(int val) { const int maxDeapth = 9; await Task.Delay(val).ConfigureAwait(false); if (val < maxDeapth) { await MethodAsync(val + 1).ConfigureAwait(false); } return val; } }