javascript - 在 GNOME 扩展中运行异步函数

标签 javascript asynchronous gnome gnome-shell-extensions gjs

我想在 GNOME 扩展中运行循环。调用 DBus 服务方法后,但 gnome shell 卡住

我了解到extensions run in the GLib's Main Loop我应该使用 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) {
      ...
    }
  }
}

最佳答案

可能有多种方法可以避免阻塞主循环,但最好的方法可能取决于什么事件会导致中断 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。

对于高级语言绑定(bind)来说,这是一个更安全的选择,并且还包括用 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()) 负责安排回调在你的主循环中。所有 I/O 都是在专用线程中完成的,因此处理输出的工作都是非阻塞的。

当您最终删除对 this._proc 的引用时,您可以放心,所有管道和资源都已正确清理,避免悬空文件描述符、僵尸进程等。如果您需要提前退出进程,可以随时调用Gio.Subprocess.force_exit()。读取循环本身将保留对 stdout 管道包装器的引用,因此您可以让它继续执行其操作。

关于javascript - 在 GNOME 扩展中运行异步函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59939821/

相关文章:

javascript - 将逗号分隔值的字符串转换为多维 JavaScript 数组?

javascript - 在 Typescript 中获取枚举的类名

javascript - 如何在 javascript 中将异步调用转换为同步调用?

javascript - 无效的 JWT token 导致 500 内部服务器错误

c# - 一点一点地更改 asp.net 应用程序以使用异步

java - 批量从线程返回值

gnome - 在 Ubuntu 中为 GNOME 3 创建应用程序启动器

c - 错误: unknown type name ‘GTKWidget’

java - Android 设备监视器未找到 JDK 路径

javascript - 我不想运行函数,只是将其用作参数,但它不断弹出