2015-03-26 2 views
3

Я предоставил обратный вызов сторонней библиотеке, которая в разное время вызывает предоставленный метод, предоставляя мне объект, который был изменен. Затем я выполняю асинхронный веб-запрос, чтобы получить дополнительные сведения и установить их на этом объекте, ниже приведен пример аналогичного примера;Как заблокировать существующие асинхронные веб-запросы?

public void update(Person person) { 
    if (person.getId() == -1) { 
     mService.getPersonDetails() 
       .flatMap(..) 
       .skip(..) 
       .subscribe(personResult -> person.setId(personResult.getId())) 
    } 
} 

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

Как синхронизировать этот вызов метода, чтобы только один запрос отправлялся для каждого объекта, который прошел через обратный вызов? Я только хочу блокировать запросы для этого точного объекта, поэтому, если update() предоставляет разные объекты, было бы нормально отправлять новые запросы.

+0

Установить флаг при запуске запроса (или перед его запуском) и очистить его при его сбое. Перед запуском нового запроса отметьте флажок (и убедитесь, что он всегда очищается при завершении работы/завершении). IdentityHashMap может использоваться для управления защитой флага для каждого экземпляра. – user2864740

+0

Там могут быть более аккуратные способы справиться с этим, возможно, с помощью Futures (или другого управления асинхронным) вместо простых флагов, но на каком-то уровне это бит, который происходит - запоминание (и проверка) того, что представляет собой действие. Конечная цель, вероятно, не должна заключаться в том, чтобы «блокировать» вызов (звучит как пробка!), Но для того, чтобы принять или перенести (или сохранить некоторые комбинированные заявленные), в зависимости от желаемого поведения. – user2864740

ответ

3

Решение, предоставленное Adam S, выглядит хорошо, но вскоре или позже вызовет проблемы с OOM. Это связано с отдельным оператором, который должен хранить все уникальные значения.

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

private Map<Person, Boolean> map = new ConcurrentHashMap<>(); 
    public void update(final Person person) { 
    if (person.getId() == -1) { 
     if(map.putIfAbsent(person, true)==null){ 
      mService.getPersonDetails() 
       .flatMap(..) 
       .skip(..) 
       .doOnTerminate(()->map.remove(person)) 
       .subscribe(personResult -> person.setId(personResult.getId())) 
     } 
    } 
} 
+0

Я обновил свой ответ, чтобы обойти потенциальные проблемы OOM (вплоть до точки, конечно). Тем не менее, вы обходите его. –

+0

Я не думаю, что это будет работать? putIfAbsent выбрасывает npe, ключ не существует? – user3032878

+0

, чтобы работать правильно, у человека должен быть метод равных значений. putIfAbsent возвращает true, если элемент был фактически добавлен к карте –

-1

Ну, вы можете просто просто synchronize.

public synchronized void update(Person person) 
+0

Я пробовал это, все еще посылает два запроса! – user3032878

+0

Хорошо. Как вы называете этот метод? –

+1

Я считаю, что проблема заключается в том, что getPersonDetails async .. поэтому обновление заканчивается до завершения запроса. – user3032878

1

Вы можете фильтровать вклад в ваш наблюдаемый с помощью оператора distinct. Вот общее представление о том, как вы могли бы сделать это с помощью (JavaDoc) (обратите внимание, это написано по памяти, я не проверял это):

private PublishSubject<Person> personSubject; 
public void update(Person person) { 
    if (personSubject == null) { 
     personSubject = new PublishSubject(); 
     personSubject 
      .filter(person -> person.getId() == -1) 
      .distinct() 
      .flatMap(person -> mService.getPersonDetails()) 
      .skip(..) 
      .subscribe(personResult -> person.setId(personResult.getId())); 
    } 
    personSubject.onNext(person); 
} 

Вы, конечно, придется либо реализовать equals на вашем Person классе (который, как указывает Марек, приведет ко всем объектам, переданным в кеширование в памяти) или реализовать вариант distinct(Func).

Этот метод использует функцию «key selector», используемую для различения объектов. Если ваши объекты довольно тяжелые, и вы обеспокоены памятью (например, если вы на Android), это может быть лучшим путем. Что-то вроде этого:

.distinct(new Func1<Person, Integer>() { 
       @Override 
       public Integer call(Person person) { 
        return person.hashCode(); 
       } 
      }) 
+0

Я обновил это, чтобы включить проверку идентификатора в наблюдаемом цепочке 'filter (person -> person.getId() == -1) 'и решение проблемы памяти, отмеченное @Marek, что делает ее в целом намного приятнее. –

+0

Это приятное решение, однако память будет критической. Кроме того, объекты-пользователи имеют ограниченный срок службы, а новый Person может иметь один и тот же идентификатор объекта. Поэтому я думаю, что отдельные блокируют новый объект, у которого есть переработанные идентификаторы. – user3032878

+0

А, ладно. Это будет хорошо работать для уникальных идентификаторов во время типичной операции «sync», которая получает настройку и срывается, но, возможно, не для чего-то долгого времени, и если ваши идентификаторы не уникальны, то сохранение ссылки _only_ во время выполнения запроса наилучшим образом достигнутым (я думаю) методом Марека. –

Смежные вопросы