Кодови договори и асинхронност

Какъв е препоръчителният начин за добавяне на постусловия към асинхронни методи, които връщат Task<T>?

Прочетох следното предложение:

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

Публикацията предлага прилагането на всеки метод като синхронен, свиването му и след това прилагането на асинхронен аналог като проста обвивка. За съжаление не виждам това като работещо решение (може би поради собственото ми неразбиране):

  1. Методът async, въпреки че се предполага, че е обвивка за метода за синхронизиране, е оставен без реален договор за код и следователно може да прави каквото си иска.
  2. Кодовите бази, които са ангажирани с асинхронност, е малко вероятно да внедрят синхронизиращи двойници за всичко. В резултат на това внедряването на нови методи, които съдържат awaits върху други асинхронни методи, следователно са принудени да бъдат асинхронни. Тези методи са вътрешно асинхронни и не могат лесно да бъдат преобразувани в синхронни. Те не са просто опаковки.

Дори ако обезсилихме последната точка, като казахме, че можем да използваме .Result или .Wait() вместо await (което всъщност би довело до блокиране на някои SyncContexts и така или иначе ще трябва да бъдат пренаписани в async метода), аз все още съм убеден за първа точка.

Има ли някакви алтернативни идеи или има нещо, което съм пропуснал относно кодовите договори и TPL?


person Lawrence Wagerfield    schedule 06.02.2012    source източник
comment
Никой не е казал, че MVP не могат да сбъркат.   -  person Panagiotis Kanavos    schedule 07.02.2012


Отговори (3)


Посочих това на екипа на Async, както други направиха. Понастоящем договорите и Async са (почти) взаимно изключващи се. Така че поне някои хора в Microsoft са наясно с проблема, но не знам какво планират да направят по въпроса.

Не препоръчвам писането на асинхронни методи като обвивки за синхронизиращи методи. Всъщност бих склонил да направя обратното.

Предварителните условия могат да работят. Не съм го пробвал скоро; може да се нуждаете от малка обвивка около вашия асинхронен метод, която включва предварителните условия.

Постусловията са доста нарушени.

Твърденията и предположенията работят нормално, но статичната проверка е наистина ограничена, защото постусловията са нарушени.

Инвариантите нямат толкова смисъл в света на Async, където променливото състояние просто пречи. (Async леко ви отблъсква от ООП към функционален стил).

Надяваме се, че във VS vNext договорите ще бъдат актуализирани с асинхронен вид постусловие, което също би позволило на статичната проверка да работи по-добре с твърдения в асинхронни методи.

Междувременно можете да имате pretend-postcondition, като напишете предположение:

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

Някои употреби на кодови договори просто не са възможни - например указване на постусловия за асинхронни членове на интерфейси или базови класове.

Лично аз току-що избегнах изцяло договорите в моя Async код, надявайки се, че Microsoft ще го поправи след няколко месеца.

person Stephen Cleary    schedule 07.02.2012
comment
Споменахте, че се надявате Microsoft да го поправи след няколко месеца Ситуацията промени ли се от момента, в който публикувахте това? Все още ли избягвате договори за асинхронни методи? - person julealgon; 15.04.2015
comment
@julealgon: За съжаление, не. Все още избягвам договори за асинхронни методи. И все още се надявам, че MS ще оправи това. :) - person Stephen Cleary; 15.04.2015
comment
Оттогава ситуацията се промени. Вижте отговора ми по-долу. - person Gediminas Zimkus; 21.11.2016

Написах това, но забравих да натисна "Публикуване"... :)

В момента няма специализирана поддръжка за това. Най-доброто, което можете да направите, е нещо подобно (без използване на ключова дума async, но същата идея - възможно е преписвачът да работи по различен начин под асинхронния CTP, все още не съм го пробвал):

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

Това обаче означава, че методът „async“ всъщност няма да се върне, докато Задачата не завърши оценката, така че „обработката“ няма да бъде отпечатана, докато не изтекат 3 секунди. Това е подобно на проблема с методите, които лениво връщат IEnumerables, договорът трябва да изброи всички елементи в IEnumerable, за да гарантира, че условието е изпълнено, дори ако повикващият всъщност няма да използва всички елементи.

Можете да заобиколите това, като промените режима на вашите договори на Preconditions, но това означава, че никакви постусловия всъщност няма да бъдат проверени.

Статичната проверка също не може да свърже Result с ламбда, така че ще получите съобщение „Гарантира недоказано“. (По принцип статичната проверка така или иначе не доказва неща за ламбда/делегати.)

Мисля, че за да получи подходяща поддръжка за Tasks/await, екипът на Code Contracts ще трябва да добави специални случаи Tasks, за да добави проверката на предварителното условие само при достъп до полето Result.

person porges    schedule 07.02.2012
comment
Благодаря ви за информацията - дори не се бях замислял за lazy-loaded колекции :-/ - person Lawrence Wagerfield; 07.02.2012
comment
Да, можете да включите превключвател (пропускане на квантори), който ще игнорира Contract.ForAll договори, за да избегнете проблеми с тях. Няма такъв превключвател за Задачи (все още). - person porges; 08.02.2012

Публикуване на нов отговор в тази стара тема, тъй като е върнат от google като първи отговор на въпрос относно CodeContract и Async

В момента Contract on async методи, връщащи Task, работят правилно и няма нужда да ги избягвате.

Стандартен договор за асинхронен метод:

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

Ако мислите, че този договор не изглежда правилно, съгласен съм, че изглежда най-малкото подвеждащо, но работи. И не изглежда, че преписвачът на договори принуждава оценката на задачата преждевременно.

Тъй като Стивън повдигна някои съмнения, направи още тестове и договорите в моя случай свършиха работата си правилно.

Код, използван за тестване:

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;
    }
}
person Gediminas Zimkus    schedule 21.11.2016
comment
Но не можете да изразите договори като цяло число ще бъде в диапазона [5, 10) и вярвам, че предварителните условия, изразени в тялото на изпълнение, също не работят според очакванията. - person Stephen Cleary; 21.11.2016
comment
Това не работи за мен. Ако имам асинхронен метод, който връща Task<T> и пиша Contract.Ensures(Contract.Result<T>() != null) в началото, това предизвиква BadImageFormatException. - person piedar; 10.02.2017