Подтвердить что ты не робот

Кодовые контракты и асинхронность

Каков рекомендуемый способ добавления постусловий к методам async, которые возвращают Task<T>?

Я прочитал следующее предложение:

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

Сообщение предлагает реализовать каждый метод как синхронный, сжимающий его, а затем реализовать асинхронный экземпляр в качестве простой оболочки. К сожалению, я не рассматриваю это как приемлемое решение (возможно, из-за моего собственного недоразумения):

  • Асинхронный метод, хотя предполагается, что он является оболочкой для метода синхронизации, остается без какого-либо реального кодового контракта и поэтому может делать то, что он хочет.
  • Кодовые базы, которые привязаны к асинхронности, вряд ли реализуют синхронизирующие копии для всего. В результате внедрение новых методов, содержащих await для других методов async, следовательно, должно быть асинхронным. Эти методы по сути являются асинхронными и не могут быть легко преобразованы в синхронные. Они не просто обертки.

Даже если мы отменили последнее утверждение, сказав, что мы могли бы использовать .Result или .Wait() вместо await (что фактически вызвало бы тупик в некотором SyncContext, и его нужно было бы переписать в async метод в любом случае), я все еще уверен в первом вопросе.

Есть ли альтернативные идеи или есть что-то, что я пропустил о кодовых контрактах и ​​TPL?

4b9b3361

Ответ 1

Я указал это на команду Async, как это сделали другие. В настоящее время контракты и Async (почти) взаимоисключающие. Таким образом, по крайней мере некоторые люди в Microsoft знают о проблеме, но я не знаю, что они планируют делать с ней.

Я не рекомендую писать асинхронные методы как обертки для методов синхронизации. Фактически, я хотел бы сделать обратное.

Предпосылки могут работать. Я не пробовал это недавно; вам может понадобиться небольшая обертка вокруг вашего асинхронного метода, который включает предварительные условия.

Постусловия в значительной степени нарушены.

Утверждения и допущения работают нормально, но статическая проверка действительно ограничена, поскольку постословия нарушены.

Инварианты не имеют особого смысла в мире Async, где изменяемое состояние имеет тенденцию просто мешать. (Async аккуратно отталкивает вас от ООП и к функциональному стилю).

Будем надеяться, что в VS vNext контракты будут обновлены с помощью типа postcondition, поддерживающего асинхронную настройку, что также позволит статической проверке работать лучше с утверждениями в методах async.

В то же время вы можете иметь притворство-постусловие, написав предположение:

// 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 исправит его через несколько месяцев.

Ответ 2

Набрал это, но забыл нажать "Опубликовать"...:)

На данный момент не существует специализированной поддержки для этого. Лучшее, что вы можете сделать, это что-то вроде этого (не используя ключевое слово 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 секунд. Это похоже на проблему с методами, которые лениво возвращают IEnumerable — Контракт должен перечислять все элементы в IEnumerable, чтобы убедиться, что условие выполнено, даже если вызывающий объект фактически не использует все элементы.

Вы можете обойти это, изменив режим контрактов на Preconditions, но это означает, что пост-условия не будут проверены.

Статический контролер также не может подключить Result к лямбда, поэтому вы получите сообщение "Обеспечивает недоказанное". (В общем, статический контролер не все равно доказывает информацию о лямбда/делегатах.)

Я думаю, что для правильной поддержки задач/ожиданий команда Code Contracts будет иметь специальные задачи, чтобы добавить проверку предварительного условия только при доступе в поле Result.

Ответ 3

Отправка нового ответа на этот старый поток, поскольку он возвращается Google как первый ответ на вопрос о CodeContract и Async

В то время как Контракт на методы 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;
    }
}