2012-02-06 19 views
17

¿Cuál es la forma recomendada para agregar condiciones posteriores a los métodos asíncronos que devuelven Task<T>?Contratos de código y asincronía

He leído la siguiente sugerencia:

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

El puesto sugiere la aplicación de cada método sincrónico, contraerla, y luego implementar una contraparte asíncrono como un simple envoltorio. Por desgracia, no veo esto como una solución viable (tal vez a través de mi propia incomprensión):

  1. El método asíncrono, aunque se supone que es una envoltura para el método de sincronización, se deja sin ningún contrato de código real y puede, por tanto, haz lo que quieras
  2. Es poco probable que las bases de datos que se comprometen con la asincronía implementen contrapartidas de sincronización para todo. Como resultado, la implementación de nuevos métodos que contienen await s en otros métodos asíncronos se ve forzado, por consiguiente, a ser asincrónico. Estos métodos son intrínsecamente asincrónicos y no se pueden convertir fácilmente a sincrónicos. No son simplemente envoltorios.

Incluso si invalidado este último punto diciendo que podríamos utilizar .Result o .Wait() en lugar de await (lo que en realidad causar algunos SyncContext s a un punto muerto, y no tendría que ser re-escrito en el método asíncrono de todos modos), Todavía estoy convencido del primer punto.

¿Hay alguna idea alternativa, o hay algo que me haya olvidado de los contratos de código y TPL?

+1

Nadie dijo MVP no puede equivocarse. –

Respuesta

14

He señalado esto al equipo Async, como han hecho otros. Actualmente, los Contratos y Async son (casi) mutuamente exclusivos. Por lo tanto, al menos algunas personas en Microsoft son conscientes del problema, pero no estoy al tanto de lo que planean hacer al respecto.

No recomiendo escribir métodos async como wrappers para métodos de sincronización. De hecho, yo tendería a hacer lo opuesto.

Las condiciones previas pueden funcionar. No lo he probado recientemente; es posible que necesite una pequeña envoltura alrededor de su método asíncrono que incluya las condiciones previas.

Las postcondiciones están casi rotas.

Las aserciones y suposiciones funcionan normalmente, pero el verificador estático es realmente limitado porque las postcondiciones están rotas.

Los invariantes no tienen tanto sentido en el mundo Async, donde el estado mutable tiende a interponerse en el camino. (Async te aleja suavemente de OOP y hacia un estilo funcional).

Esperemos que en VS vNext, los contratos se actualizarán con un tipo de condición asincrónica, que también permitiría que el verificador estático funcione mejor con aserciones en métodos asíncronos.

Mientras tanto, usted puede tener una condición posterior pretender escribiendo un asumir:

// 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 ...; 
} 

Algunos usos de los contratos de código simplemente no son posibles - por ejemplo, especificando condiciones posteriores de los miembros asincrónicos de interfaces o clases base .

Personalmente, acabo de evitar los contratos por completo en mi código Async, con la esperanza de que Microsoft lo solucione en unos meses.

+0

Mencionó que esperaba "que Microsoft lo solucione en unos meses" ¿Cambió la situación desde que publicó esto? ¿Todavía evitas contratos con métodos asíncronos? – julealgon

+2

@julealgon: Desafortunadamente, no. Todavía evito contratos en métodos asíncronos. Y todavía espero que MS arregle esto. :) –

+0

La situación ha cambiado desde entonces. Mira mi respuesta a continuación. –

2

mecanografiado esto, pero se olvidó de golpear "Post" ... :)

No hay soporte especializado para esto en el momento. Lo mejor que puede hacer es algo como esto (no usar async de palabras clave, pero la misma idea - es posible la re-escritura funcionará de manera diferente bajo la CTP asíncrono, no he probado todavía):

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); 
} 

Sin embargo, esto significa que el método 'async' no regresará hasta que la Tarea haya terminado de evaluar, por lo que el "procesamiento" no se imprimirá hasta que hayan transcurrido 3 segundos. Esto es similar al problema con los métodos que devuelven vagamente IEnumerable s — el Contrato tiene que enumerar todos los elementos en el IEnumerable para garantizar que la condición se mantenga, incluso si la persona que llama no va a utilizar todos los elementos.

Puede solucionar esto cambiando su modo de contrato a Preconditions, pero esto significa que no se verificarán las condiciones posteriores.

El verificador estático tampoco puede conectar el Result con el lambda, por lo que aparecerá el mensaje "No se ha comprobado". (En general, el verificador estático no prueba las cosas sobre lambdas/delegados de todos modos.)

Creo que para obtener el soporte adecuado para tareas/aguardar, el equipo de contratos de código tendrá tareas especiales para agregar el control de precondición únicamente al acceder al campo Result.

+0

Gracias por la información, ni siquiera había pensado en las colecciones cargadas de forma diferida: -/ –

+0

Sí, puede activar un conmutador (omitir cuantificadores) que ignorará los contratos 'Contrato.ParaTodos' para evitar problemas con ellos. No hay tal cambio para Tareas (todavía). – porges

0

Publicar nueva respuesta a este hilo de edad, ya que es devuelto por Google como la primera respuesta a la pregunta sobre CodeContract y asíncrono

Curently del contrato en los métodos asincrónicos que regresan de tareas están trabajando correctamente, y no hay necesidad de evitarlos .

contrato Standart para el método asíncrono:

[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; 
    } 
} 

Si cree que el contrato no parece correcto Estoy de acuerdo en que parece inducir a error al menor, pero funciona. Y no parece que ese reescritor de contrato fuerce la evaluación de la tarea prematuramente.

Como Stephen planteó algunas dudas hizo algunas pruebas más y los contratos en mi caso hicieron su trabajo correctamente.

Código utilizado para la prueba:

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

Pero no puede expresar contratos como "el número entero estará en el rango [5, 10)", y creo que las condiciones previas expresadas en el organismo de implementación tampoco funcionan como se esperaba. –

+0

Eso no funciona para mí. Si tengo un método asíncrono que devuelve 'Tarea ' y escribo 'Contract.Ensures (Contract.Result ()! = Null)' al principio, causa una 'BadImageFormatException'. – piedar

Cuestiones relacionadas