Постановка в очередь обновлений ObservableCollection

Я программирую приложение TAPI, которое использует шаблон состояния для работы с различными состояниями, в которых может находиться TK. Входящие и исходящие вызовы записываются через ObservableCollection в ListView (журнал вызовов). Данные о звонках сравниваются с контактами, хранящимися в базе данных SQL-Server, для определения возможных совпадений. Затем эта информация используется для обновления журнала вызовов. Все это, конечно, в реальном времени и все управляется/в разных состояниях FSM (конечный автомат).

Чтобы различать вызовы, я использую идентификатор вызова (который предоставляется TAPI). Когда звонит телефон или я начинаю звонить, в журнал вызовов добавляется новая запись, включая идентификатор вызова, и в базе данных клиентов выполняется поиск номера, и определенные данные в журнале обновляются соответствующим образом. При переходе через различные состояния вызова приложение динамически обновляет журнал (т. е. меняет значок, который визуально показывает состояние конкретного вызова и т. д.).

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

Будет ли какая-то очередь сообщений возможным/хорошим решением? Если да, то как можно реализовать такую ​​очередь сообщений - в контексте конечного автомата или ObservableCollection. Я не ищу полных решений, но буду признателен за любую информацию, которую я не могу легко найти через Google или stackoverflow.

Изменить: вопрос сильно перефразирован.

Изменить: я добавил собственное решение проблемы, но подожду и посмотрю, может быть, у кого-то есть идея получше.


person Sascha Hennig    schedule 17.10.2011    source источник
comment
Кстати, вам нужно добавить break к вашему foreach, чтобы сделать его эквивалентным FirstOrDefault.   -  person Branko Dimitrijevic    schedule 17.10.2011
comment
О, конечно, спасибо. Отредактировал мой пост, чтобы отразить эту информацию. Хотя это не меняет исхода в этом особом контексте.   -  person Sascha Hennig    schedule 17.10.2011
comment
Перефразировал вопрос, чтобы прояснить основную проблему (поскольку теперь она стала немного яснее для меня.   -  person Sascha Hennig    schedule 17.10.2011


Ответы (2)


Вы проверили, является ли результат FirstOrDefault null? Это может произойти, если в коллекции нет элемента с заданным id.

Например:

var element = this.FirstOrDefault(p => p.ID == id);
if (element != null) {
    // Do something with element.Number.
}

Или вы можете просто позвонить First и посмотреть, получите ли вы InvalidOperationException.

--- ИЗМЕНИТЬ ---

Из вашего комментария я вижу, что вы, похоже, одновременно обращаетесь к одному и тому же ObservableCollection из нескольких потоков. Если это так, вам необходимо защитить общую структуру данных с помощью блокировки. Вполне возможно, что один поток начинает вставлять новый элемент как раз в тот момент, когда другой его ищет, что приводит к разного рода неопределенному поведению. Согласно документации MSN для ObservableCollection:

"Не гарантируется, что любые члены экземпляра будут потокобезопасными."

Что касается отладки, вы можете «заморозить» другие потоки и таким образом сосредоточиться только на интересующем вас потоке без чрезмерных «прыжков». См. панель «Потоки», контекстное меню, параметры «Заморозить» и «Разморозить».

person Branko Dimitrijevic    schedule 17.10.2011
comment
Приложение на самом деле никогда не заходит так далеко, исключение выдается в первой строке вашего примера (ну, именно в запросе LINQ). Как я уже упоминал, this — это ObservableCollection — пользовательского класса. Может ли быть так, что если какой-либо член этого класса имеет значение null FirstOrDefault, будет выброшено это исключение NullReferenceException (даже если оно НЕ является свойством ID)? Думаю, мне нужно будет написать небольшое тестовое приложение, чтобы проверить, так ли это. - person Sascha Hennig; 17.10.2011
comment
@SaschaHennig Вы должны иметь возможность установить точку останова непосредственно в делегате (p => p.ID == id). Он будет срабатывать для каждого элемента коллекции, но в конечном итоге вы доберетесь до того, у кого возникла проблема, и, надеюсь, сможете ее диагностировать. - person Branko Dimitrijevic; 17.10.2011
comment
Причина, по которой у меня возникли проблемы с отладкой, заключалась в том, что приложение сильно многопоточно и перескакивает между потоками, что также является причиной того, что значение в журнале вызовов не обновляется до того, как другой поток попытается его проверить. Нужно было прояснить проблему с потоками в моем первом посте. Тем не менее +1, так как без ваших сообщений я бы, вероятно, не продвинулся так далеко в процессе решения проблем, как сейчас. Думаю реализовать какую-то очередь, которая обновляет журнал звонков. - person Sascha Hennig; 17.10.2011
comment
Хотя я не использовал этот (намного более простой и, следовательно, лучший) подход в то время, так как мне очень понравилась идея реализации шаблона команды, на самом деле это отличный и правильный ответ. Шаблон команды - это просто более сложный способ, на самом деле не блокирующий, а блокирующий выполнение потока (относительно моей реализации). Я определенно должен был отметить этот ответ намного раньше. Я до сих пор очень счастлив, что пошел своим путем в то время, так как многому научился, что не меняет вышеупомянутых фактов. Так что лучше поздно, чем никогда. Спасибо! - person Sascha Hennig; 12.08.2014

Обновление ObservableCollection — длительный процесс, по крайней мере, по сравнению с получением и обработкой событий TAPI. Это может привести к состояниям гонки, когда состояние вызова, которое должно было бы отредактировать запись вызова, не могло найти запись, поскольку оно требовало блокировки для записи/обновления коллекции до состояния вызова, которое фактически должно было бы добавить вызов. Кроме того, неправильная обработка событий TAPI в надлежащем порядке приведет к поломке конечного автомата.

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

В общем, все TAPI-события (вызывающий) добавляются в очередь (клиент) в том порядке, в котором они происходят, как можно быстрее. Затем очередь передает информацию TAPI получателю, конечному автомату, выполняющему бизнес-логику, не торопясь, но следя за тем, чтобы информация обновлялась в правильном порядке.

Изменить. Начиная с .NET 4.0 вы можете использовать ConcurrentQueue (T) Class для достижения того же результата.

person Sascha Hennig    schedule 19.10.2011