运行 GNOME 扩展中的一个异步函数
Running an asynchronous function in a GNOME extension
我想 运行 GNOME 扩展中的一个循环。在调用 DBus 服务方法后,但 gnome shell 冻结
我知道extensions run in the GLib's Main Loop and I should use GTask API,但我找不到使用它的方法,也没有明确的例子。
我想我不能使用GLib.spawn_async_with_pipes
因为我不想调用一个命令,而是在同一个class
中调用一个函数
代码示例:
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
循环。
如果您确实需要 "polling" 某些条件,我认为最简单的方法可能是超时循环:
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()
) 负责为您安排主循环中的回调。所有 I/O 都在专用线程中完成,因此您处理输出的工作都是非阻塞的。
当您最终删除对 this._proc
的引用时,您可以放心,所有管道和资源都已正确清理,避免悬挂文件描述符、僵尸进程等。如果需要提前退出进程,可以随时调用Gio.Subprocess.force_exit()
。读取循环本身将保留对标准输出管道包装器的引用,因此您可以让它自行其是。
我想 运行 GNOME 扩展中的一个循环。在调用 DBus 服务方法后,但 gnome shell 冻结
我知道extensions run in the GLib's Main Loop and I should use GTask API,但我找不到使用它的方法,也没有明确的例子。
我想我不能使用GLib.spawn_async_with_pipes
因为我不想调用一个命令,而是在同一个class
代码示例:
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
循环。
如果您确实需要 "polling" 某些条件,我认为最简单的方法可能是超时循环:
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()
) 负责为您安排主循环中的回调。所有 I/O 都在专用线程中完成,因此您处理输出的工作都是非阻塞的。
当您最终删除对 this._proc
的引用时,您可以放心,所有管道和资源都已正确清理,避免悬挂文件描述符、僵尸进程等。如果需要提前退出进程,可以随时调用Gio.Subprocess.force_exit()
。读取循环本身将保留对标准输出管道包装器的引用,因此您可以让它自行其是。