Запуск асинхронной функции в расширении GNOME

Я хочу запустить цикл внутри расширения GNOME. после вызова метода службы DBus но оболочка gnome зависает

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

Я думаю, что не могу использовать GLib.spawn_async_with_pipes, потому что я хочу вызывать не команду, а функцию в том же классе

Пример кода:

class Foo {

  constructor () {
    this._ownName()
  }
  
  _ownName () {
    Gio.bus_own_name(
      Gio.BusType.SESSION,
      'net.foo',
       Gio.BusNameOwnerFlags.NONE,
      this._onBusAcquired.bind(this),
      this._noOp,
      this._noOp
    )
  }

  _onBusAcquired(connection) {
    connection.register_object(
    '/net/foo',
    GioInterface,
    this._methodCallClosure.bind(this),
    this._noOp,
    this._noOp
  )
  
  _methodCallClosure(connection, sender, objectPath, interfaceName,
                     methodName, parameters, invocation) {
    this._listen = true
    this._runLoop()

    invocation.return_value(null)
  }

  // this function should not block GLib's Main Loop
  _runLoop () {
    while(this._listen) {
      ...
    }
  }
}


person Antonio Tapiador    schedule 27.01.2020    source источник
comment
Вы работаете на стороне клиента или на стороне сервера? Другими словами, является ли class Foo службой DBus или оболочкой для клиента DBus?   -  person andy.holmes    schedule 28.01.2020
comment
Или ваше желание запустить цикл совершенно не связано с использованием DBus?   -  person andy.holmes    schedule 28.01.2020


Ответы (1)


Вероятно, есть несколько способов избежать блокировки основного цикла, но лучший из них, вероятно, зависит от того, какое событие приведет к разрыву цикла while.

Если вам действительно нужно «опросить» какое-то условие, самым простым подходом, я думаю, будет цикл тайм-аута:

function myPollingFunc(arg1, arg2) {
    if (the_event_occured) {
        // Here is where you will invoke whatever it is you want to do
        // when the event occurs, then destroy the source since the
        // condition has been met.
        this.classMethod(arg1, arg2);

        return GLib.SOURCE_REMOVE;
    }

    // If the condition was not met, you can wait until the next loop and
    // check again.
    return GLib.SOURCE_CONTINUE;
}

// Probably you'll want to store this ID (maybe as a property on your class),
// so you can destroy the source if the DBus service is destroyed before the
// event you're waiting for occurs.
let sourceId;

sourceId = GLib.timeout_add_seconds(
    // Generally this priority is fine, but you may choose another lower one
    // if that makes sense
    GLib.PRIORITY_DEFAULT,

    // The timeout interval of your loop. As described in the linked article,
    // second-based loops tend to be a better choice in terms of performance,
    // however note that at least one interval will pass before the callback
    // is invoked.
    1,

    // Your callback function. Since you're probably creating the source from
    // a class method and intend on modifying some internal state of your class
    // you can bind the function to the class scope, making it's internal state
    // and other methods available to your callback.
    //
    // Function.bind() also allows you to prepend arguments to the callback, if
    // that's necessary or a better choice. As noted above, you'll probably want
    // to store the ID, which is especially important if you bind the callback to
    // `this`.
    // 
    // The reason is that the callback will hold a reference to the object it's
    // bound to (`this` === `Foo`), and the source will hold the callback in the
    // loop. So if you lose track of the source and the ability to destroy it, the
    // object will never be garbage collected.
    myPollingFunc.bind(this, arg1, arg2)
);

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

С другой стороны, может быть более простой способ сделать это, если вам на самом деле не нужно опрашивать условие, но вы ожидаете отправки сигнала или изменения свойства GObject:

...

  _runLoop () {
    // Waiting for some object to emit a signal or change a property
    let handlerId = someObject.connect('some-signal', () => {
        someObject.disconnect(handlerId);

        this.classMethod(arg1, arg2);
    });

    let propId = someGObject.connect('notify::some-prop', () => {
        if (someGObject.some_prop === 'expected_value') {
            someGObject.disconnect(propId);

            this.classMethod(arg1, arg2);
        }
    });
  }

...

Не зная, какого типа событие или условие вы ожидаете, трудно дать лучший совет по лучшему решению, но, возможно, это поможет. В общем, я бы сказал, что если вы думаете, что хотите зациклиться на каком-то условии, вам лучше проверить это условие в существующем основном цикле GLib, чем создавать свой собственный подцикл.

ИЗМЕНИТЬ

С большим контекстом кажется, что вы так или иначе будете полагаться на внешний процесс. В этом случае я бы настоятельно рекомендовал избегать низкоуровневых функций порождения в GLib и вместо этого использовать GSubprocess.

Это гораздо более безопасный выбор для языковых привязок более высокого уровня, а также включает вспомогательные функции, написанные на C, которые вы, скорее всего, все равно перепишете:

onProcExited(proc, result) {
    try {
        proc.wait_check_finish(proc, result);
    } catch (e) {
        logError(e);
    }
}

onLineRead(stdout, result) {
    try {
        let line = stdout.read_line_finish_utf8(result)[0];

        // %null generally means end of stream
        if (line !== null) {
            // Here you can do whatever processing on the line
            // you need to do, and this will be non-blocking as
            // all the I/O was done in a thread.
            someFunc(line);

            // Now you can request the next line
            stdout.read_line_async(null, onLineRead.bind(this));
        }
    } catch (e) {
        logError(e);
    }
}

startFunc() {
    this._proc = new Gio.Subprocess({
        argv: ['proc_name', '--switch', 'arg', 'etc'],
        flags: Gio.SubprocessFlags.STDOUT_PIPE
    });
    this._proc.init(null);

    // Get the stdout pipe and wrap it in a buffered stream
    // with some useful helpers
    let stdout = new Gio.DataInputStream({
        base_stream: this._proc.get_stdout_pipe()
    });

    // This function will spawn dedicated a thread, reading and buffering
    // bytes until it finds a line ending and then invoke onLineRead() in
    // in the main thread.
    stdout.read_line_async(
        GLib.PRIORITY_DEFAULT,
        null // Cancellable, if you want it
        onLineRead.bind(this)
    );

    // Check the process completion
    this._proc.wait_check_async(null, onProcExited.bind(this));
}

Написание рекурсивного цикла чтения, подобного этому, довольно прямолинейно, и функция GTask (*_async()/*_finish()) позаботится о планировании обратных вызовов в основном цикле для вас. Весь ввод-вывод выполняется в выделенном потоке, поэтому вся работа по обработке вывода не блокируется.

Когда вы в конечном итоге отбросите ссылку на this._proc, вы можете быть уверены, что все каналы и ресурсы очищены должным образом, избегайте оборванных файловых дескрипторов, зомби-процессов и так далее. Если вам нужно выйти из процесса раньше, вы всегда можете позвонить Gio.Subprocess.force_exit(). Сам цикл чтения будет содержать ссылку на оболочку канала stdout, поэтому вы можете просто оставить его делать свое дело.

person andy.holmes    schedule 29.01.2020
comment
Извините, @andy.holmes, я сделал комментарий с большим контекстом, но после редактирования проблем он был полностью удален. Служба DBus прослушивает вызов метода из openHab, чтобы начать слушать шум в микрофоне. Цикл непрерывно обрабатывает звук с микрофона и ищет шум. При шуме расширение разблокирует экран и ждет тишины. По этой причине я не хочу блокировать основной цикл: пользователь должен взаимодействовать, пока микрофон слушает. Может быть, мне, наконец, следует переместить микрофон, слушающий (asound | sox), на GLib.spawn_async - person Antonio Tapiador; 29.01.2020
comment
Обновлен ответ с примером того, как вы можете использовать GSubprocess для обработки внешнего процесса, не блокируя основной цикл. - person andy.holmes; 30.01.2020
comment
Очень исчерпывающие ответы с большим количеством деталей. Я собирался спросить об очистке ресурсов, проблеме, которая возникает при использовании GLib.spawn_async, но я вижу, что Gio.Subprocess также решает эту проблему. Определенно кажется, что это правильный путь. - person Antonio Tapiador; 31.01.2020