2012-02-06 17 views
17

Qual è il modo consigliato per aggiungere postcondizioni a metodi asincroni che restituiscono Task<T>?Contratti di codice e asincronia

aver letto la seguente proposta:

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

Il post suggerisce attuazione ciascun metodo come sincrono, contraendo e quindi attuare una controparte asincrona come semplice involucro. Purtroppo non vedo questo come una soluzione praticabile (forse attraverso la mia incomprensione):

  1. Il metodo asincrono, anche se presume essere un wrapper per il metodo di sincronizzazione, viene lasciato senza alcun contratto di codice reale e può dunque fai come desidera
  2. È improbabile che i codebase impegnati in asincronia implementino per tutto le controparti di sincronizzazione. 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 invalidato quest'ultimo punto dicendo potremmo usare .Result o .Wait() invece di await (che sarebbe in realtà causare alcuni SyncContext s a un punto morto, e avrebbe dovuto essere riscritto nel metodo asincrono in ogni caso), Sono ancora convinto del primo punto.

Ci sono idee alternative o c'è qualcosa che mi è sfuggito riguardo ai contratti di codice e TPL?

+1

Nessuno ha detto MVP non può sbagliare. –

risposta

14

Ho segnalato questo 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 è consigliabile scrivere metodi asincroni come wrapper per i metodi di sincronizzazione. In effetti, tenderei a fare il contrario.

I prerequisiti 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 interrotte.

Asserzioni e ipotesi funzionano normalmente, ma il controllo statico è veramente limitato perché le post-condizioni 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 vengano aggiornati con una sorta di postcondition asincrona, che consente anche al correttore statico di funzionare meglio con asserzioni in metodi asincroni.

Nel frattempo, si può avere una finta-postcondizione scrivendo una assumere:

// Synchronous version for comparison. 
public static string Reverse(string s) 
{ 
    Contract.Requires(s != null); 
    Contract.Ensures(Contract.Result<string>() != null); 

    return ...; 
} 

// First wrapper takes care of preconditions (synchronously). 
public static Task<string> ReverseAsync(string s) 
{ 
    Contract.Requires(s != null); 

    return ReverseWithPostconditionAsync(s); 
} 

// Second wrapper takes care of postconditions (asynchronously). 
private static async Task<string> ReverseWithPostconditionAsync(string s) 
{ 
    var result = await ReverseImplAsync(s); 

    // Check our "postcondition" 
    Contract.Assume(result != null); 

    return result; 
} 

private static async Task<string> ReverseImplAsync(string s) 
{ 
    return ...; 
} 

Alcuni usi dei contratti di codice semplicemente non sono possibili - per esempio, specificando postcondizioni sui membri asincroni di interfacce o classi di base .

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

+0

Hai detto che speravi "che Microsoft risolverà il problema tra qualche mese" La situazione è cambiata da quando hai postato questo? Evitate ancora i contratti sui metodi asincroni? – julealgon

+2

@julealgon: Purtroppo no. Evito ancora i contratti sui metodi asincroni. E spero ancora che la MS risolverà questo problema. :) –

+0

Situazione è cambiata da. Controlla la mia risposta qui sotto. –

2

digitato questo in su ma ha dimenticato di colpire "Post" ... :)

Non c'è supporto specializzato per questo al momento. Il meglio che puoi fare è qualcosa di simile (se non utilizzano async parola chiave, ma la stessa idea - è possibile che il masterizzatore funziona in modo diverso sotto il CTP asincrono, non ho ancora provato):

public static Task<int> Do() 
{ 
    Contract.Ensures(Contract.Result<Task<int>>() != null); 
    Contract.Ensures(Contract.Result<Task<int>>().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 articoli nello IEnumerable per garantire che la condizione sia valida, anche se il chiamante non utilizzerà effettivamente tutti gli elementi.

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

Il correttore statico non è inoltre in grado di collegare lo Result con il lambda, quindi verrà visualizzato il messaggio "Assicura non verificato". (In generale, il controllore statico non dimostra comunque nulla su lambda/delegati.)

Penso che per ottenere un supporto adeguato per le attività/attese, il team di Contratti di codice dovrà eseguire Task di caso speciale per aggiungere solo il controllo di precondizione. all'accesso del campo Result.

+0

Grazie per le informazioni - Non avevo nemmeno pensato alle collezioni pigro-caricate: -/ –

+0

Sì, è possibile attivare un interruttore (skip quantificatori) che ignorerà i contratti 'Contract.ForAll' per evitare di avere problemi con loro. Non esiste un tale interruttore per Attività (ancora). – porges

0

postano nuovi risposta a questo vecchio thread come viene restituito da Google come la prima risposta alla domanda su CodeContract e asincrone

attualmente in appalto a metodi asincroni che tornano Task, funzioni correttamente, e non v'è alcuna necessità di evitarli .

contratto Standart per il metodo asincrono:

[ContractClass(typeof(ContractClassForIFoo))] 
public interface IFoo 
{ 
    Task<object> MethodAsync(); 
} 


[ContractClassFor(typeof(IFoo))] 
internal abstract class ContractClassForIFoo : IFoo 
{ 
    #region Implementation of IFoo 

    public Task<object> MethodAsync() 
    { 
     Contract.Ensures(Contract.Result<Task<object>>() != null); 
     Contract.Ensures(Contract.Result<Task<object>>().Status != TaskStatus.Created); 
     Contract.Ensures(Contract.Result<object>() != null); 
     throw new NotImplementedException(); 
    } 

    #endregion 
} 

public class Foo : IFoo 
{ 
    public async Task<object> MethodAsync() 
    { 
     var result = await Task.FromResult(new object()); 
     return result; 
    } 
} 

Se si pensa che il contratto non sembra corretto Sono d'accordo che sembra fuorviante per lo meno, ma funziona. E non sembra che quel reporter del contratto costringa prematuramente la valutazione del compito.

Come Stephen ha sollevato qualche dubbio, altri test e contratti nel mio caso hanno fatto correttamente le loro cose.

codice utilizzato per il test:

public static class ContractsAbbreviators 
{ 
    [ContractAbbreviator] 
    public static void EnsureTaskIsStarted() 
    { 
     Contract.Ensures(Contract.Result<Task>() != null); 
     Contract.Ensures(Contract.Result<Task>().Status != TaskStatus.Created); 
    } 

} 

[ContractClass(typeof(ContractClassForIFoo))] 
public interface IFoo 
{ 
    Task<int> MethodAsync(int val); 
} 

[ContractClassFor(typeof(IFoo))] 
internal abstract class ContractClassForIFoo : IFoo 
{ 
    public Task<int> MethodAsync(int val) 
    { 
     Contract.Requires(val >= 0); 
     ContractsAbbreviators.EnsureTaskIsStarted(); 
     Contract.Ensures(Contract.Result<int>() == val); 
     Contract.Ensures(Contract.Result<int>() >= 5); 
     Contract.Ensures(Contract.Result<int>() < 10); 
     throw new NotImplementedException(); 
    } 
} 

public class FooContractFailTask : IFoo 
{ 
    public Task<int> MethodAsync(int val) 
    { 
     return new Task<int>(() => val); 
     // esnure raises exception // Contract.Ensures(Contract.Result<Task>().Status != TaskStatus.Created); 
    } 
} 

public class FooContractFailTaskResult : IFoo 
{ 
    public async Task<int> MethodAsync(int val) 
    { 
     await Task.Delay(val).ConfigureAwait(false); 
     return val + 1; 
     // esnure raises exception // Contract.Ensures(Contract.Result<int>() == val); 
    } 
} 

public class Foo : IFoo 
{ 
    public async Task<int> MethodAsync(int val) 
    { 
     const int maxDeapth = 9; 

     await Task.Delay(val).ConfigureAwait(false); 

     if (val < maxDeapth) 
     { 
      await MethodAsync(val + 1).ConfigureAwait(false); 
     } 

     return val; 
    } 
} 
+0

Ma non è possibile esprimere contratti come "il numero intero sarà nell'intervallo [5, 10)", e ritengo che le precondizioni espresse nel corpo dell'implementazione non funzionino come previsto. –

+0

Questo non funziona per me. Se ho un metodo asincrono che restituisce 'Task ' e scrivo 'Contract.Ensures (Contract.Result ()! = Null)' all'inizio causa una 'BadImageFormatException'. – piedar

Problemi correlati