Итерируя по коллекции обработчиков событий, как безопасно удалить обработчик из * внутри * обратного вызова? - PullRequest
3 голосов
/ 05 марта 2011

Я немного озадачен чем-то.Документация Java говорит нам, что не существует определенного поведения при удалении элементов из коллекции во время итерации по этой коллекции с использованием объекта Iterator, и что единственный безопасный способ сделать это - использовать Iterator.remove ().

Как тогда, вы бы безопасно удалили обработчик событий из ArrayList, если в ходе итерации по списку один из обработчиков решил, что пришло время удалить себя в качестве слушателя?

// in public class Dispatcher

public void dispatchEvent(){
    Iterator<IEventHandler> iterator = mHandlers.iterator();
    IEventHandler handler = null;
    while(iterator.hasNext()){
        handler = iterator.next();
        handler.onCallbackEvent();
    }
}

public void insertHandler(IEventHandler h){
    mHandlers.add(h);
}

public void removeHandler(IEventHandler h){
    mHandlers.remove(h);
}

Между тем обработчик был создан следующим образом ...

final Dispatcher d = new Dispatcher();
d.insertHandler(new IEventHandler(){
    @Override
    public void onCallbackEvent(){
        Log.i(" callback happened ");
        d.removeHandler(this);
    }
});

Видите потенциальную проблему?Вы удаляете обработчик из ArrayList в результате onCallbackEvent (), объявленного в этом конкретном обработчике , в то время как вы все еще выполняете итерацию с использованием Iterator .

Это неразрешимая проблема?Какой безопасный способ справиться с этой ситуацией?

Ответы [ 2 ]

3 голосов
/ 05 марта 2011

Это очень распространенная проблема при внедрении системы событий. Единственное решение - скопировать список обработчиков при изменении. Вы можете сделать это самостоятельно в методах insertHandler / removeHandler или просто использовать CopyOnWriteArrayList.

2 голосов
/ 26 июля 2011

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

public void removeHandler(IEventHandler h){
    mHandlersToRemove.add(h);
}

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

public void dispatchEvent(){
    mHandlers.removeAll(mHandlersToRemove);
    mHandlersToRemove.clear();
    ...

Вы также можете удалить по адресуконец dispatchEvent, но тогда вы можете удалить только из в обработчиках .(В противном случае вы можете отправить обработчики, которые были удалены.)


Если вы заинтересованы в теоретическом решении этой проблемы, вы можете посмотреть, как C ++ реализует итераторы.В векторе stl итератор имеет метод стирания , который возвращает следующий действительный итератор.

Это будет выглядеть примерно так:

for (itr = listA.begin(); itr != listA.end(); )
{
    if ( shouldRemove(*itr) ) {
        itr = listA.erase(itr);
    }
    else {
      ++itr;
    }
}

Конечно, этопример не относится к вашему вопросу, так как он написан на C ++, и было бы неудобно распространять новый итератор до цикла верхнего уровня (или добавлять возвращаемое значение к вашему вызову для условия «удалить»).Но, может быть, где-то есть похожая реализация Java:)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...