在遍历事件处理程序集合时,如何安全地从回调函数内部删除处理程序?

3

我对一件事感到有些困惑。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);
    }
});

看到潜在问题了吗?在使用迭代器进行迭代时,由于在特定处理程序中声明的onCallbackEvent(),你正在从ArrayList中删除处理程序。 这是一个无法解决的问题吗? 如何安全地处理这种情况?

2个回答

3

在实现事件系统时,这是一个非常常见的问题。唯一的解决方案是在更改时复制处理程序列表。您可以在insertHandler/removeHandler方法中自行执行此操作,或者只需使用CopyOnWriteArrayList。


谢谢您的建议。这确实很不幸。我不禁想知道,如果迭代器在迭代时不能提供一种安全的方式来修改底层集合,那么它的存在意义是什么。**注意:是的,我知道迭代器设计模式。然而,如果您费心使用迭代器,似乎这种安全性应该是迭代器应该提供给您的东西。 - scriptocalypse
为了给你想要的迭代安全性,迭代器必须复制或执行同样计算密集的操作。你能否自己想出另一种实现安全迭代器的方式?你要么在更改时进行复制,要么在迭代时进行复制。在大多数情况下,更改时复制更为合适,因为更改比迭代更少。默认集合不提供迭代安全性,因为并非所有情况都需要它。这就是为什么有ArrayList和CopyOnWriteArrayList的原因。 - Konstantin Komissarchik
1
“复制change的handlers列表”:您的意思是要在removeHandler中复制数组,然后在dispatchEvent结束时用修改后的副本替换原始列表吗?(使用CopyOnWriteArrayList似乎更简单。) - idbrii
“复制更改处理程序列表”意味着在调用addListener/removeListener方法时复制列表,而不是在事件分派期间进行复制。 - Konstantin Komissarchik

2
您可以重新实现removeHandler函数,以存储计划删除的处理程序。
public void removeHandler(IEventHandler h){
    mHandlersToRemove.add(h);
}

在进行任何分派之前,请先删除它们。

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

你也可以在 dispatchEvent 结尾处移除,但那样的话只能从处理程序内部删除。(否则可能会触发已被删除的处理程序。)
如果你对解决这个问题有兴趣,可以看看 C++ 如何实现迭代器。在 stl 向量中,迭代器具有返回下一个有效迭代器的 erase 方法
它看起来像这样:
for (itr = listA.begin(); itr != listA.end(); )
{
    if ( shouldRemove(*itr) ) {
        itr = listA.erase(itr);
    }
    else {
      ++itr;
    }
}

当然,这个例子并不适用于你的问题,因为它既是C++的,而且将新的迭代器向上传播到顶层循环会很尴尬(或者在调用中添加返回值以进行“删除”条件)。但也许有一个类似的Java实现存在:)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接