如果一个模块是异步的,那么不要向其暴露任何你无法包装的同步方法。
有很多时候我们希望一些独立于核心逻辑的模块,或者说插件,能够异步地运行。这样可以最大幅度地利用系统资源。但是有时插件可能会需要访问同步的资源。直接把同步的方法暴露给插件并且不在文档中说明然后在插件异步访问同步资源时报错是最错误的做法,说的就是你 Minecraft Server Implementations! 下面,我们探寻一些可能的从逻辑上而言正确的实现。
内部加锁
如果我们不能要求外部插件时时刻刻记得在访问同步资源时加锁,那么我们在 API 内部进行包装似乎是一个好主意:
let semaphore = 1;
function someSyncApiWorker() {
// private function that do the actual work
// ...
}
function someSyncApi() {
P(semaphore);
someSyncApiWorker();
V(semaphore);
}
function myPlugin() {
someSyncApi();
someOtherOperations();
}
这样可以保证同步的函数总是被同步调用。但是这样做有一个缺点:锁会降低插件的效率,或者说会卡死插件线程。这是我们不希望看到的。
内部序列
那么我们可以维护一个内部的调用序列,使得插件可以立刻返回:
let callQueue = [];
function someSyncApiWorker() {
while (!callQueue.isEmpty()) {
let call = callQueue.shift();
call.doOperation();
}
}
function someSyncApi() {
callQueue.push({ ... });
}
function myPlugin() {
someSyncApi();
someOtherOperations();
}
这样插件就可以快速返回。但是如果说插件之后要运行的代码依赖于这个同步函数对上下文作出的更改呢?
回调函数
差不多所有现代编程语言都能够实现「回调」这样一个功能。对于 Java 8 和 JS 而言,回调是自然的。
function someSyncApiWorker() {
// ...
}
function someSyncApi(callback: Consumer<?>) {
let result = someSyncApiWorker();
callback.invoke(result);
}
function myPlugin() {
someSyncApi((result) => {
someOtherOperations(result);
});
}
回调本身也可以被包装成 Promise
或者 Future
:
function someSyncApiWorker() {
// ...
}
function someSyncApi() {
return new Promise((resolve) => {
resolve(someSyncApiWorker());
}
}
function myPlugin() {
someSyncApi().then(result => someOtherOperations(result));
}
总结
代码垃圾,摊手。
Your comments will be submitted to a human moderator and will only be shown publicly after approval. The moderator reserves the full right to not approve any comment without reason. Please be civil.