Избегайте ошибок распределения с помощью destroy() и асинхронных функций

Ниже приведен простой сценарий для некоторого расширения GNOME:

  1. Включите расширение. Расширение — это класс, расширяющий Clutter.Actor.
  2. Он создает актера с именем myActor и добавляет его: this.add_child(myActor).
  3. Затем он вызывает асинхронную трудоемкую функцию this._tcFunction(), которая в конце концов что-то делает с myActor.

Вот тут я столкнулся с проблемой:

  1. Отключаем (запускаем this.destroy()) расширение сразу после включения.

  2. При отключении this.destroy() запускает this.run_dispose() GObject для сбора мусора. Однако, если this._tcFunction() не закончил работу, позже он попытается получить доступ к myActor, который, возможно, уже был освобожден this.run_dispose().

Один из способов сделать это — определить логическую переменную в this.destroy().

destroy() {
    this._destroying = true
    // ...
    this.run_dispose;
}

а затем добавьте проверку в this._tcFunction(), например.

async _tcFunction() {
    await this._timeConsumingStuff();

    if (this._destroying === true) { return; }

    myActor.show();

}

Мой вопрос: есть ли лучший способ справиться с этими ситуациями? Может с Gio.Cancellable()? Насколько я знаю, нет простого способа остановить асинхронную функцию в javascript...


person Juozas Miškinis    schedule 24.04.2020    source источник


Ответы (1)


Во-первых, две вещи, о которых нужно знать:

  1. Избегайте вызова низкоуровневых функций управления памятью, таких как GObject.run_dispose(), поскольку в библиотеках C есть случаи, когда эти объекты кэшируются для повторного использования и на самом деле не удаляются, как вы думаете. Также нет сигнала удаления, и другим объектам может потребоваться уведомление.

  2. Избегайте переопределения функций, которые запускают удаление, таких как Clutter.Actor.destroy(), и вместо этого используйте сигнал уничтожения. Обратный вызов сигнала GObject всегда получает испускающий объект в качестве первого аргумента, и безопасно использовать это в обратном вызове уничтожения.

Есть несколько способов решить эту проблему, но это зависит от ситуации. Если асинхронная функция является асинхронной функцией библиотеки GNOME, она, вероятно, имеет отменяемый аргумент:

let cancellable = new Gio.Cancellable();

let actor = new Clutter.Actor();
actor.connect('destroy', () => cancellable.cancel());

Gio.File.new_for_path('foo.txt').load_contents_async(cancellable, (file, res) => {
    try {
        let result = file.load_contents_finish(res);

        // This shouldn't be necessary if the operation succeeds (I think)
        if (!cancellable.is_cancelled())
            log(actor.width);
    } catch (e) {
        // We know it's not safe
        if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
            log('it was cancelled');

        // Probably safe, but let's check
        else if (!cancellable.is_cancelled())
            log(actor.width);
    }
});

// The above function will begin but not finish before the
// cancellable is triggered
actor.destroy();

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

new Promise((resolve, reject) => {
   // Some operation
   resolve();
}).then(result => {
    // Check the cancellable
    if (!cancellable.is_cancelled())
        log(actor.width);
});

Другой вариант — null вывести вашу ссылку, так как вы можете безопасно проверить это:

let actor = new Clutter.Actor();
actor.connect('destroy', () => {
    actor = null;
});

if (actor !== null)
    log(actor.width);
person andy.holmes    schedule 24.04.2020
comment
Спасибо за совет не использоватьrun_dispose(), я не знал о таком кешировании. Что касается обработки ошибок, я в итоге использовал if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) в функциях, которые включали асинхронные вызовы. Задача решена. - person Juozas Miškinis; 25.04.2020