Как связаться с изолятом, выполняющим операцию блокировки в Dart / Flutter?

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

  ReceivePort rPort;
  SendPort sPort;
  Isolate isolate;

void main() {
  rPort = ReceivePort();
  isolate = await Isolate.spawn(entryPoint, receivePort.sendPort);
  sPort = await rPort.first;
  sendMessage("perform"); // this is being processed
  sendMessage("controlMessage"); // this is being processed after the perform has ended
}

void sendMessage(String msg) {
  ReceivePort localReceivePort = ReceivePort();
  sendPort.send([msg, localReceivePort.sendPort]);
}

void entryPoint(SendPort sendPort)
{
 ReceivePort receivePort = ReceivePort();
 sendPort.send(receivePort.sendPort);

 receivePort.listen((message) {
   String data = msg[0];
   SendPort reply = msg[1];
   print(data);
   if(data == "perform") performBlockingOperation();
   else if(data == "controlMessage") controlPerformance();
   repy.send("something"); 
 });

}

Я пробовал много вариантов: сделать performBlockingOperation асинхронным, но, похоже, это не сработало. Даже попытка создать собственный поток внутри изолята возвращает

../../third_party/dart/runtime/vm/runtime_entry.cc: 3331: ошибка: невозможно вызвать собственный обратный вызов за пределами изолированного объекта.

Есть ли способ добиться того, что я пытаюсь?


person Philiste    schedule 26.04.2020    source источник
comment
Я хотел бы по-прежнему иметь возможность общаться с ним, пока он работает. - чтобы было ясно: вы хотите быть в каком-то большом вычислительном цикле в вашем Isolate и по-прежнему получать входящие сообщения? или вы хотите получить следующее сообщение в очереди после завершения одного большого цикла вычислительной задачи?   -  person pskink    schedule 26.04.2020
comment
Первый вариант: я хочу находиться в большом вычислительном цикле в Isolate и по-прежнему получать входящие сообщения.   -  person Philiste    schedule 26.04.2020
comment
ну, это невозможно, если вы не добавите несколько контрольных точек в свой цикл, проверяющий, прибыли ли какие-то новые сообщения - но я действительно сомневаюсь, что вы можете это сделать (или можете?)   -  person pskink    schedule 26.04.2020
comment
Да, я могу, долгая вычислительная функция - это просто длинный цикл. Я могу поместить в цикл все, что захочу.   -  person Philiste    schedule 26.04.2020
comment
так что добавьте в свой цикл await Future(() {});, чтобы дать receivePort.listen() шанс выполнить свою работу   -  person pskink    schedule 26.04.2020
comment
Вы хотите написать await Future(() {listenAndDoSomething});? Потому что с пустым оператором Future он, похоже, здесь не работает (я думал, что пустое await Future, как вы написали, затем приостановит выполнение, чтобы дать слушателю Isolate время для прослушивания). Я продолжу расследование.   -  person Philiste    schedule 27.04.2020
comment
см. paste.ubuntu.com/p/qJZF64R4y7   -  person pskink    schedule 27.04.2020
comment
Несколько тестов, которые я провел, работают не иначе, чем моя предыдущая реализация. Но я заметил, что даже простой print внутри цикла производительности не печатается, что странно. Так что у меня может возникнуть другая проблема с реализацией. Глядя на это, я вернусь, как только смогу это исправить.   -  person Philiste    schedule 27.04.2020
comment
так ты не видишь print('step $i');? как придешь? Вы успешно создали изолят?   -  person pskink    schedule 27.04.2020
comment
Да, изолятор работает. Код: while(performAudioBlock()) { print("step $i"); }.   -  person Philiste    schedule 27.04.2020
comment
сначала запустите мой код без каких-либо изменений, убедитесь, что вы вызываете channel.sink.add('input data') из основного потока в течение первых 10 шагов цикла изоляции - конечно, вы можете вызывать channel.sink.add несколько раз   -  person pskink    schedule 27.04.2020
comment
Да, я сделал это. Я почти уверен, что он исходит не из изолятора. У меня нет времени на это сейчас, но завтра сделаю тесты! Спасибо за вашу помощь   -  person Philiste    schedule 27.04.2020
comment
Ваш код здесь отлично работает. Проблема для меня может заключаться в том, что while(performAudioBlock()){} выходит за пределы цикла событий изоляции. В зависимости от размера аудиоблока он вычисляет 512 двойных 40 раз в секунду. Есть ли способ узнать, происходит ли это переполнение?   -  person Philiste    schedule 28.04.2020
comment
Потому что внутри цикла while ничего не обрабатывается.   -  person Philiste    schedule 28.04.2020
comment
У меня была опечатка где-то в ffi собственного API, который я оборачиваю, поэтому он обертывал неправильную функцию - поэтому он выполнял весь звук (например, 20 секунд), а не только один аудиоблок перед входом в цикл. Теперь отлично работает с вашим изолированным каналом. Спасибо за помощь !   -  person Philiste    schedule 28.04.2020
comment
отлично !, я думаю, это можно было бы сделать проще, используя SchedulerBinding, но если текущий подход сработает ...   -  person pskink    schedule 28.04.2020
comment
И как вы думаете, это было бы более эффективно с точки зрения вычисления времени?   -  person Philiste    schedule 29.04.2020


Ответы (1)


Сделать вычисления асинхронными - это первый правильный шаг. Второй шаг - фактически передать управление в некоторые моменты во время вычислений.

Затем вставьте несколько операторов await Future.delayed(Duration.zero); в свой вычислительный код, который блокирует все остальное. Не слишком глубоко в вычислениях, потому что они вносят задержку, но достаточно часто, чтобы у вашего изолята была возможность время от времени проверять наличие новых событий.

person lrn    schedule 26.04.2020
comment
Спасибо за этот ответ. Я не уверен, что делать со вторым шагом. Это про заявление о доходности? И это внутри функции или контроля производительности (основной изолятор)? - person Philiste; 26.04.2020
comment
Никаких yield заявлений не требуется. Оператор await должен находиться внутри функции производительности, кода, который выполняется и не позволяет изолированному объекту делать что-либо еще. Добавляя задержку внутри этого кода, задержку, достаточную для того, чтобы цикл событий мог проверять наличие других событий, вы гарантируете, что другой код также сможет запустить. - person lrn; 27.04.2020