2012-02-06 15 views
17

Jaki jest zalecany sposób dodawania warunków do asynchronicznych metod, które zwracają Task<T>?Umowy na kod i asynchrony

Czytałem następujące sugestie:

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

Post proponuje wdrożenie każdej metody jako synchroniczny, zamawiających go, a następnie wdrożenie odpowiednika asynchronicznej jako prosty owijki. Niestety nie widzę tego jako realne rozwiązanie (być może za pośrednictwem własnego niezrozumienia):

  1. Sposób asynchroniczny, choć zakłada się wrapper dla metody synchronizacji, zostanie pozostawiony bez jakiejkolwiek rzeczywistej umowy kodu i może dlatego rób, co chcesz.
  2. Codebases, które są zaangażowane w asynchronię, prawdopodobnie nie zaimplementują synchronizowanych odpowiedników we wszystkim. W rezultacie wdrożenie nowych metod zawierających inne asynchroniczne metody jest konsekwentnie zmuszane do asynchronizacji. Metody te są z natury asynchroniczne i nie można ich łatwo przekonwertować na synchroniczne. Nie są to po prostu opakowania.

Nawet jeśli unieważnione ten ostatni punkt, mówiąc, moglibyśmy użyć .Result lub .Wait() zamiast await (który faktycznie będzie powodować pewne SyncContext s do impasu, a musiałaby być ponownie napisane w sposób asynchroniczny i tak), Nadal jestem przekonany o pierwszym punkcie.

Czy są jakieś alternatywne pomysły, czy jest coś, co przeoczyłem w związku z umowami na kod i TPL?

+1

Nikt nie powiedział MVP nie może się to źle. –

Odpowiedz

14

Wskazałam to zespołowi Async, tak jak zrobili to inni. Obecnie kontrakty i asynchroniczne (prawie) wzajemnie się wykluczają. Tak więc przynajmniej niektórzy ludzie w firmie Microsoft zdają sobie sprawę z tego problemu, ale nie wiem, co zamierzają z tym zrobić.

Nie polecam pisania metod asynchronicznych jako obwolut dla metod synchronizacji. W rzeczywistości miałbym tendencję do czynienia czegoś odwrotnego.

Warunki wstępne mogą działać. Ostatnio tego nie próbowałem; możesz potrzebować małego opakowania wokół metody asynchronicznej, która zawiera warunki wstępne.

Postgrecje są prawie zepsute.

Asercje i założenia działają normalnie, ale sprawdzanie statyczne jest naprawdę ograniczone, ponieważ warunki postojowe są zepsute.

Inwariaty nie mają większego sensu w świecie Async, gdzie stan zmienny ma tendencję do wchodzenia w drogę. (Async delikatnie odsuwa cię od OOP i do stylu funkcjonalnego).

Mam nadzieję, że w VS vNext, Kontrakty będą aktualizowane w asyncowym rodzaju warunku, który umożliwi również sprawdzenie statyczne lepiej z asercjami w metodach asynchronicznych.

W międzyczasie można mieć udawać-postcondition pisząc zakładają:

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

Niektóre zwyczaje umów kodu po prostu nie są możliwe - np określające postconditions na asynchronicznych członków interfejsy lub klas bazowych .

Osobiście właśnie uniknąłem umów w całości w moim kodzie asynchronicznym, mając nadzieję, że Microsoft naprawi je za kilka miesięcy.

+0

Wspomniałeś, że masz nadzieję, że "Microsoft naprawi je za kilka miesięcy". Czy sytuacja zmieniła się od tego kiedy to opublikowałeś? Czy nadal unikasz umów na metody asynchroniczne? – julealgon

+2

@julealgon: Niestety, nie. Wciąż unikam umów na metody asynchroniczne. I wciąż mam nadzieję, że SM to naprawi. :) –

+0

Sytuacja zmieniła się od. Sprawdź moją odpowiedź poniżej. –

2

Wpisane to w górę, ale zapomniałem hit „post” ... :)

Nie ma wyspecjalizowane wsparcie dla tego w tej chwili. Najlepsze co możesz zrobić, to coś takiego (nie używając async słowa kluczowego, ale sama idea - jest to możliwe, nagrywarka będzie działać w różny sposób w ramach CTP asynchronicznym, ja nie próbowałem go jeszcze):

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

jednak oznacza to, że metoda "asynchroniczna" w rzeczywistości nie powróci, dopóki zadanie nie zakończy oceny, więc "przetwarzanie" nie zostanie wydrukowane przed upływem 3 sekund. Jest to podobne do problemu z metodami, które leniwie zwracają IEnumerable s — Umowa musi wyliczyć wszystkie elementy w IEnumerable, aby upewnić się, że warunek jest spełniony, nawet jeśli osoba wywołująca nie używa wszystkich elementów.

Możesz obejść ten problem, zmieniając tryb umowy na Preconditions, ale oznacza to, że żadne warunki końcowe nie będą sprawdzane.

Sprawdzenie statyczne również nie może połączyć się z Result z lambda, więc otrzymasz komunikat "Zapewnia niepotwierdzony". (Ogólnie rzecz biorąc, sprawdzanie statyczne nie udowadnia w żaden sposób rzeczy związanych z lambdami/delegatami).

Myślę, że aby uzyskać odpowiednie wsparcie dla zadań/czekania, zespół ds. Umów kodowych będzie musiał wykonać zadania specjalne, aby dodać tylko kontrolę warunków wstępnych przy dostępie do pola Result.

+0

Dziękuję za informację - nie myślałem nawet o leniwych kolekcjach: -/ –

+0

Tak, możesz włączyć przełącznik (pomiń kwantyfikatory), który zignoruje kontrakty 'Contract.ForAll', aby uniknąć problemów z nimi. Nie ma takiego przełącznika dla zadań (jeszcze). – porges

0

zamieszczaniu nową odpowiedź do tego starego wątku jak jest ona zwracana przez Google jako pierwsza odpowiedź na pytanie o CodeContract i Async

curently zamówienia w asynchronicznych metod powracających zadanie pracują prawidłowo i nie ma potrzeby, aby ich uniknąć .

kontrakt Standart dla metody asynchronicznej:

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

Jeśli uważasz, że umowa nie wyglądać prawidłowa zgadzam wygląda mylące co najmniej, ale to nie działa. I nie wygląda na to, aby przepisujący kontrakt wymusił przedwczesną ocenę zadania.

W związku z tym, że Stephen podniósł pewne wątpliwości, wykonał jeszcze kilka testów, a kontrakty w moim przypadku prawidłowo wykonały swoje zadanie.

kod używany do testowania:

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

Ale nie można wyrazić umów, takich jak "liczba całkowita będzie w przedziale [5, 10)" i uważam, że warunki wstępne wyrażone w organie wdrażającym również nie działają zgodnie z oczekiwaniami. –

+0

To nie działa dla mnie. Jeśli mam metodę async zwracającą 'Task ' i piszę 'Contract.Ensures (Contract.Result ()! = Null) na początku, powoduje to' BadImageFormatException'. – piedar

Powiązane problemy