Возможно ли, чтобы функция с сопрограммой Kotlin дождалась завершения и вернулась в класс Java?

#java #android #kotlin #kotlin-coroutines

Вопрос:

Я работаю над приложением для Android на базе Java. Я хочу начать использовать Kotlin и сопрограммы для работы с сетью вне основного потока, но я столкнулся с проблемой, пытаясь понять, как успешно справиться с определенной ситуацией.

Я создал файл Kotlin с сетевой функцией, которую я планирую вызвать в классе Java (модель представления, которая в настоящее время должна оставаться написанной на Java). Идея заключается в том, что я хочу, чтобы сетевая функция изменила и вернула список обратно в модель представления, а затем позволила модели представления продолжить.

 fun performNetworking(
    data: MutableList<Data>
): MutableList<Data> {
    CoroutineScope(Dispatchers.IO).launch {
        data.forEach {
            // modify each element in the list
        }
    }
    return data
}
 

В настоящее время я вызываю его из модели представления Java как таковой:

     public void modifyData(List<Data> data) {
        List<Data> modifiedData = NetworkingKt.performNetworking(data);
        performMoreThings(modifiedData);
    }
 

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

Поэтому мне интересно, есть ли способ заставить код дождаться завершения работы области сопрограммы, прежде чем возвращать список данных в модель представления Java (без блокировки основного потока и предотвращения взаимодействия пользователя с приложением)? Или я неправильно понимаю возможности, цели и реализации сопрограмм, и это невозможно? Должен ли я передать ссылку на модель представления и заставить ее вызвать метод продолжения после завершения сопрограммы, как я сделал бы с AsyncTask в its onPostExecute() ?

Комментарии:

1. Почему бы не performNetworking() быть а suspend fun ? Вы можете использовать withContext(Dispatchers.IO) , чтобы сказать, что работа performNetworking() выполняется в подходящих потоках. Затем modifyData можно использовать viewModelScope (или какой-либо другой подходящий CoroutineScope ) для вызова performNetworking() и получения результата.

2. @CommonsWare Я не уверен, заметили ли вы, но modifyData() это метод Java, а не Котлин. Не так просто даже запустить сопрограмму с Java (определенно возможно, просто не так тривиально), и даже если у нас все еще есть точно такая же проблема, описанная в описании: нам нужно дождаться результата, но ожидание блокирует поток.

3. @broot: Ак! Да, я скучал по этому. Я слишком привык к проектам, завершающим миграцию Java -> Kotlin, прежде чем приступать к сопрограммам.

Ответ №1:

Нет, это невозможно. Не из — за ограничений сопрограмм, а наоборот-из-за ограничений не сопрограммного кода. Ваш modifyData() метод является обычным, не приостанавливаемым, поэтому он все равно не может ждать, не блокируя поток. Приостановить функции Kotlin может сделать это, но методы Java не могут.

Вам нужно либо вызвать modifyData() из фонового потока, чтобы блокировка не была проблемой, либо перепроектировать performNetworking() , чтобы использовать один из классических методов для решения асинхронных задач, т. Е. Получить обратный вызов или вернуть будущее:

 fun performNetworking(
    data: MutableList<Data>
): CompletableFuture<MutableList<Data>> {
    return CoroutineScope(Dispatchers.IO).future {
        data.forEach {
            // modify each element in the list
        }
        data
    }
}
 
 public void modifyData(List<Data> data) {
    NetworkingKt.performNetworking(data)
            .thenAccept(this::performMoreThings);
}
 

Комментарии:

1. Возвращение в будущее действительно интересно, и, похоже, это решило бы мою проблему. Наш минимальный API-23, и я узнал, что для этого требуется API 24. Тогда я бы предположил, что использование обратного вызова-мой единственный вариант. Вопрос независимо от этого: я, кажется, не могу позвонить .future , как вы сделали в своем примере. Есть идеи, почему? Помимо этого, если бы я преобразовал свою модель представления в Kotlin, я понимаю, что я мог бы создать performNetworking() suspend fun , а затем вызвать ее в ViewModelScope и использовать .async и await() выполнить то, что я хочу сделать. Согласуется ли это с вашим пониманием?

2. Как CompletableFuture было добавлено в Java 8, future() предусмотрено в отдельной библиотеке: kotlinx-coroutines-jdk8 — вам нужно добавить зависимость для нее. Обратные вызовы также хороши, и да, при использовании сопрограмм мы обычно предпочитаем выставлять длительные операции в качестве приостанавливаемых функций. Однако, если modifyData() предполагается дождаться performNetworking() , то имеет больше смысла сделать внешнюю функцию также приостанавливаемой, а затем вызывать performNetworking() напрямую.