Code Contracts и async – вместе "веселее".

Рассмотрим две ситуации, которые могут произойти, если в проекте одновременно используются асинхронные методы и контракты.

Актуальная версия Code Contracts для данного материала – 1.7.10908.11. Скорее всего, описанные ошибки в дальнейшем будут исправлены.

Постусловия в async

Как многим хорошо известно, использование async/await раскрывает выброшенные из асинхронного кода AggregateException. Поэтому код с ними зачастую выглядит примерно так:

try {
    …
    var result = await someService.SomeActionAsync(currentData);
    …
}
catch (ArgumentException ex) {
    // Обработка ошибки
}

Однако, при использовании CodeContracts такой код может начать выдавать AggregateException. И тогда код может превратиться в что-то вроде:

try {
    …
    var result = await someService.SomeActionAsync(currentData);
    …
}
catch (ArgumentException ex) {
    // Обработка ошибки
}
catch (AggregateException ex) {
    var argumentException = ex.InnerExceptions.FirstOrDefault(e => e is ArgumentException);
    if (argumentException != null) {
        // Обработка ошибки
    }
    else throw;
}

На первый взгляд это похоже на ошибку работы await, который перестал раскрывать AggregateException. Но не будем спешить с выводами и посмотрим на метод SomeActionAsync(). Скорее всего он содержит постусловие, например:

Contract.Ensures(0 < Contract.Result<int>());

В этом случае в генерируемом коде класса, который его содержит, можно найти следующую обертку для проверки контрактов:

[CompilerGenerated]
private class <SomeAction>AsyncContractClosure_0
{
    public <SomeAction>AsyncContractClosure_0() { }

    public int CheckPost(Task<int> task)
    {
        Task<int> task1 = task;
        int result = task.Result;
        if (__ContractsRuntime.insideContractEvaluation <= 4) {
            try {
                __ContractsRuntime.insideContractEvaluation = __ContractsRuntime.insideContractEvaluation + 1;
                __ContractsRuntime.Ensures(0 < task1.Result, null, "0 < Contract.Result<int>()");
            }
            finally {
                __ContractsRuntime.insideContractEvaluation = __ContractsRuntime.insideContractEvaluation - 1;
            }
        }
        return result;
    }
}

Вызов task.Result приводит к тому, что исходное AggregateException (из самого метода) оборачивается в еще одно AggregateException. Используемый await честно раскрывает внешнее и передает вложенное AggregateException дальше.

На данный момент решений данной проблемы два: отказаться от постусловий или писать обработку как конкретных исключений, так и AggregateException.

Пропадание переменных в отладке

Еще один не приятный эффект – пропадание значений переменных при пошаговой отладке. Visual Studio не может отобразить их даже в режиме Debug. Такое поведение замечено в методах, содержащих несколько вызовов await у которых есть контракты. Судя по всему, CodeContracts сильно изменяют код с помощью CCRewrite.

Решение для данной проблемы – временное отключение CodeContracts для отладки метода, где наблюдается такое поведение.

Добавить комментарий