Я работаю с новой кодовой базой, которая интенсивно использует async / await. Большинство людей в моей команде также довольно плохо знакомы с async / await. Как правило, мы склонны придерживаться рекомендаций Best Practices, указанных Microsoft , но обычно нам нужен наш контекст для прохождения асинхронного вызова и работа с библиотеками, которые этого не делают ConfigureAwait(false)
.
Объедините все эти вещи, и мы столкнемся с асинхронными тупиками, описанными в статье ... еженедельно. Они не отображаются во время модульного тестирования, потому что наших поддельных источников данных (обычно через Task.FromResult
) недостаточно, чтобы вызвать тупик. Таким образом, во время выполнения или интеграционных тестов, какой-то сервисный вызов просто уходит на обед и никогда не возвращается. Это убивает серверы и, как правило, создает беспорядок.
Проблема заключается в том, что отслеживание того, где была допущена ошибка (обычно просто не асинхронная до конца), обычно включает ручную проверку кода, которая занимает много времени и не может быть автоматизирована.
Как лучше диагностировать причину тупика?
async
статей этого парня ?Ответы:
Хорошо, я не уверен, что следующее поможет вам, потому что я сделал некоторые предположения при разработке решения, которое может быть верным или нет в вашем случае. Возможно, мое «решение» слишком теоретическое и работает только для искусственных примеров - я не проводил никаких тестов, кроме приведенных ниже.
Кроме того, я бы увидел следующее скорее обходное решение, чем реальное решение, но, учитывая отсутствие ответов, я думаю, что оно все еще может быть лучше, чем ничего (я продолжал наблюдать за вашим вопросом, ожидая решения, но не видя сообщения, опубликованного, я начал играть вокруг с вопросом).
Но достаточно сказано: допустим, у нас есть простой сервис данных, который можно использовать для получения целого числа:
Простая реализация использует асинхронный код:
Теперь возникает проблема, если мы используем код «неправильно», как показано в этом классе.
Foo
неверный доступTask.Result
вместоawait
результата, какBar
:Нам (вам) сейчас нужен способ написать тест, который успешно
Bar
выполняется при вызове, но не при вызовеFoo
(по крайней мере, если я правильно понял вопрос ;-)).Я позволю коду говорить; вот что я придумал (используя тесты Visual Studio, но он должен работать и с использованием NUnit):
DataServiceMock
используетTaskCompletionSource<T>
. Это позволяет нам установить результат в определенной точке в тестовом прогоне, что приводит к следующему тесту. Обратите внимание, что мы используем делегата, чтобы вернуть TaskCompletionSource обратно в тест. Вы также можете поместить это в метод Initialize теста и использовать свойства.Здесь происходит то, что мы сначала проверяем, что мы можем оставить метод без блокировки (это не будет работать, если кто-то получит доступ
Task.Result
- в этом случае мы столкнемся с таймаутом, поскольку результат задачи не будет доступен до тех пор, пока метод не будет возвращен ).Затем мы устанавливаем результат (теперь метод может выполняться) и проверяем результат (внутри модульного теста мы можем получить доступ к Task.Result, поскольку мы фактически хотим, чтобы блокировка происходила).
Завершить тестовый класс -
BarTest
успешно иFooTest
неудачно по желанию.И небольшой вспомогательный класс для проверки на наличие тупиков / тайм-аутов:
источник
Вот стратегия, которую я использовал в огромном и очень, очень многопоточном приложении:
Во-первых, вам нужна некоторая структура данных вокруг мьютекса (к сожалению) и не делать каталог синхронизирующих вызовов. В этой структуре данных есть ссылка на любой ранее заблокированный мьютекс. Каждый мьютекс имеет «уровень», начинающийся с 0, который вы назначаете при создании мьютекса и никогда не можете его изменить.
И правило таково: если мьютекс заблокирован, вы должны когда-либо блокировать другие мьютексы только на более низком уровне. Если вы следуете этому правилу, у вас не может быть тупиков. Когда вы обнаружите нарушение, ваше приложение все еще работает и работает нормально.
Когда вы обнаружите нарушение, у вас есть две возможности: вы могли ошибочно назначить уровни. Вы заблокировали A, а затем заблокировали B, поэтому B должен был иметь более низкий уровень. Таким образом, вы устанавливаете уровень и попробуйте снова.
Другая возможность: вы не можете это исправить. Некоторый ваш код блокирует A с последующей блокировкой B, в то время как другой код блокирует B с последующей блокировкой A. Нет способа назначить уровни, позволяющие это сделать. И, конечно, это потенциальная тупиковая ситуация: если оба кода работают одновременно в разных потоках, существует вероятность тупиковой ситуации.
После введения этого была довольно короткая фаза, где уровни должны были быть отрегулированы, сопровождаемая более длинной фазой, где были обнаружены потенциальные тупики.
источник
Используете ли вы Async / Await, чтобы вы могли распараллеливать дорогостоящие вызовы, как в базе данных? В зависимости от пути выполнения в БД это может быть невозможно.
Тестовое покрытие с помощью async / await может быть сложным, и нет ничего лучше, чем реальное использование продукта для поиска ошибок Один шаблон, который вы можете рассмотреть, это передача идентификатора корреляции и запись его в стек, а затем каскадный таймаут, который регистрирует ошибку. Это скорее шаблон SOA, но, по крайней мере, он даст вам представление о том, откуда он берется. Мы использовали это со Splunk, чтобы найти тупики.
источник