Должен я модульный тест на проблемы многопоточности прежде, чем записать какую-либо блокировку?

Можно ли без задержки запускать приложение uwp в качестве пользовательского интерфейса и использовать приложение WPF в качестве постоянной фоновой задачи, обмениваясь сообщениями взад и вперед?

blockquote>

Да, вроде. Вы можете добавить компонент расширения рабочего стола в свое приложение UWP.

@Stefan Wick написал несколько постов в блоге на эту тему, а также предоставил пример кода на GitHub , который показывает, как расширить приложение UWP с помощью «классического» консольного рабочего стола .NET. приложение, которое выполняется как процесс полного доверия.

Пожалуйста, обратитесь к его блог для получения дополнительной информации.

8
задан abatishchev 1 June 2011 в 12:15
поделиться

9 ответов

When writing multi-threaded code, you must use your brain even more than the usual. You must reason logically about every single line of code, whether it is thread safe or not. It's like proving the correctness of a mathematical formula - you can not prove things like "N + 1 > N for all N" by just giving examples of values of N with which the formula is true. Similarly, proving that a class is thread-safe is not possible by writing test cases that try to expose problems with it. With a test it's only possible to prove that there is a fault, but not that there are no faults.

The best thing that you can do, is to minimize the need for multi-threaded code. Preferably the application should have no multi-threaded code (for example by relying on thread-safe libraries and suitable design patterns), or it should be restricted to a very small area. Your StackQueue class looks like simple enough, so that you can make it safely thread-safe with a little thinking.

Assuming that the Stack and Queue implementations are thread-safe (I don't know .NET's libraries), you just need to make Next() thread-safe. Count is already thread-safe as it is, because no client can use the value returned from it safely without using client-based locking - state dependencies between methods would otherwise break the code.

Next() is not thread-safe, because it has state dependencies between methods. If threads T1 and T2 call stack.Count at the same time and it returns 1, then one of them will get the value with stack.Pop(), but the other will call stack.Pop() when the stack is empty (which then appears to throw InvalidOperationException). You will need a stack and queue with non-blocking versions of Pop() and Dequeue() (ones that return null when empty). Then the code would be thread-safe when written like this:

private WebRequestInfo Next()
{
    WebRequestInfo next = stack.PopOrNull()
    if (next == null)
    {
        next = queue.DequeueOrNull();
    }
    return next;
}
3
ответ дан 5 December 2019 в 08:25
поделиться

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

Для более серьезной проблемы с ошибками в потоке существует ШАХМАТА (в процессе) - возможно, вариант в будущем.

6
ответ дан 5 December 2019 в 08:25
поделиться

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


Хорошо, теперь, когда вы опубликовали код:

public int Count
    {
        get
        {
            return this.queue.Count + this.stack.Count;
        }
    }

Это отличный пример того, что у вас будут проблемы при написании модульного теста, который выявит проблемы с потоками в вашем коде. Этот код потенциально нуждается в синхронизации, потому что значения this.queue.Count и this.stack.Count могут изменяться в середине вычисления итога, поэтому он может возвращать значение, которое не является «правильным».

HOWEVER - Учитывая оставшуюся часть определения класса, на самом деле ничто не зависит от того, как Count дает последовательный результат, так имеет ли это значение, если он «неправильный»? Нет никакого способа узнать это, не зная, как другие классы в вашей программе используют этот. Это делает тестирование на потоки проблем интеграционным тестом, а не модульным тестом.

4
ответ дан 5 December 2019 в 08:25
поделиться

Многопоточность может привести к таким сложным проблемам, что написать для них модульные тесты практически невозможно. Возможно, вам удастся написать модульный тест, который имеет 100% -ную частоту отказов при выполнении в коде, но после того, как вы его пройдете, вполне вероятно, что в коде все еще есть условия гонки и аналогичные проблемы.

Проблема с проблемами многопоточности в том, что они возникают случайно. Даже если вы проходите модульный тест, это не обязательно означает, что код работает. Таким образом, в этом случае TDD дает ложное чувство безопасности и может даже рассматриваться как плохая вещь.

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

2
ответ дан 5 December 2019 в 08:25
поделиться

Просто чтобы прояснить, не всем классам нужны блокировки, чтобы быть потокобезопасными.

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

Не могли бы вы опубликовать более конкретную информацию о вашем классе?

1
ответ дан 5 December 2019 в 08:25
поделиться

TDD - это инструмент, и он хороший, но иногда возникают проблемы, которые не решаются с помощью определенного инструмента. Я бы предположил, что если разработка тестов слишком сложна, вы должны использовать TDD для разработки ожидаемой функциональности, но, возможно, полагаться на проверку кода, чтобы убедиться, что код блокировки, который вы добавляете, является подходящим и позволит вашему классу быть потоком -safe.

Одним из возможных решений было бы разработать код, который помещался бы внутри замка и помещал его в свой собственный метод. Тогда вы сможете подделать этот метод для проверки кода блокировки. В своем фальшивом коде вы можете просто установить ожидание, чтобы второй поток, обращающийся к коду, ожидал блокировки до завершения первого потока. Не зная точно, что делает ваш код, я не могу быть более конкретным, чем это.

2
ответ дан 5 December 2019 в 08:25
поделиться

Особенно в многоядерных системах вы можете обычно проверять наличие проблем с потоками, но не детерминистически.

Обычно я делаю это, раскручивая несколько потоков, проходящих через рассматриваемый код. и подсчитать количество неожиданных результатов. Эти потоки выполняются в течение некоторого короткого периода времени (обычно 2-3 секунды), затем используют Interlocked.CompareExchange, чтобы добавить свои результаты, и нормально завершите работу. Мой тест, который раскручивал их, затем вызывал .Join для каждого, а затем проверял, было ли количество ошибок равным 0.

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

1
ответ дан 5 December 2019 в 08:25
поделиться

Я согласен с другими авторами, что многопоточного кода следует избегать или, по крайней мере, ограничивать небольшими частями вашего приложения. Тем не менее, я все еще хотел какой-то способ проверить эти маленькие порции. Я работаю над портом .NET библиотеки Java MultithreadedTC . Мой порт называется Ticking Test , а исходный код опубликован в Google Code.

MultithreadedTC позволяет вам написать тестовый класс с несколькими методами, помеченными атрибутом TestThread. Каждый поток может ожидать поступления определенного числа тиков или утверждать, каким он считает текущий счет тиков. Когда все текущие потоки заблокированы, поток-координатор увеличивает счетчик тиков и активирует все потоки, которые ожидают следующего счетчика тиков. Если тебе интересно, посмотрите обзор MultithreadedTC для примеров. MultithreadedTC был написан теми же людьми, которые написали FindBugs .

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

1
ответ дан 5 December 2019 в 08:25
поделиться

Most unit tests operate sequentially and not concurrently, so they will not expose concurrency problems.

Code inspection by programmers with concurrency expertise is your best bet.

Those evil concurrency problems often won't show up until you have enough product in the field to generate some statistically relevant trends. They are incredibly hard to find sometimes, so it is often best to avoid having to write the code in the first place. Use pre-existing thread-safe libraries and well-established design patterns if possible.

0
ответ дан 5 December 2019 в 08:25
поделиться
Другие вопросы по тегам:

Похожие вопросы: