确保异步方法永远不会并行执行

Ensure an async method is never executed in parallel

一些外部库中有一个异步方法,它不是线程安全的(并发调用时会破坏内部状态)并且在不久的将来不太可能被修复。然而,我希望我的代码库的其余部分在任何可能的并行与顺序组合中自由使用它(换句话说,我希望使用该方法的 my 方法仍然是线程安全的) .

所以我需要用一些 Active Object 实现来包装那个有问题的方法,以便它的调用始终对齐(一个接一个地执行)。

我对此的看法是使用 Promises 链:

var workQueue: Promise<any> = Promise.resolve();

function executeSequentially<T>(action: () => Promise<T>): Promise<T> {

    return new Promise((resolve, reject) => {

        workQueue = workQueue.then(() => {
            return action().then(resolve, reject);
        });
    });
}

然后不直接调用该方法:

const myPromise = asyncThreadUnsafeMethod(123);

我会这样称呼它:

const myPromise = executeSequentially(() => asyncThreadUnsafeMethod(123));

但是我错过了什么吗?这种方法有什么潜在的问题吗?有更好的想法吗?

这种方法没有问题,使用承诺队列是一种标准模式,可以解决您的问题。

虽然您的代码工作正常,但我建议避免 Promise constructor antipattern:

let workQueue: Promise<void> = Promise.resolve();
const ignore = _ => {};
function executeSequentially<T>(action: () => Promise<T>): Promise<T> {
    const result = workQueue.then(action);
    workQueue = result.then(ignore, ignore);
    return result;
}

你还说你会像 executeSequentially(() => asyncThreadUnsafeMethod(123)); 那样“调用方法”,但这确实会导致你忘记 [=16] 的错误=] 包装器,仍然直接调用不安全的方法。相反,我建议引入一种间接方法,将整个安全调用包装在一个函数中,然后仅导出该函数,让您的代码库仅与该外观交互,而不是与库本身交互。为了帮助创建这样的外观,我会柯里化 executeSequentially 函数:

const ignore = _ => {};
function makeSequential<T, A extends unknown[]>(fn: (...args: A) => Promise<T>) => (...args: A) => Promise<T> {
    let workQueue: Promise<void> = Promise.resolve();
    return (...args) => {
        const result = workQueue.then(() => fn(...args));
        workQueue = result.then(ignore, ignore);
        return result;
    };
}

export const asyncSafeMethod = makeSequential(asyncThreadUnsafeMethod);

或者直接针对那个案例实施它

const ignore = _ => {};
let workQueue: Promise<void> = Promise.resolve();

export function asyncSafeMethod(x: number) {
    const result = workQueue.then(() => asyncThreadUnsafeMethod(x));
    workQueue = result.then(ignore, ignore);
    return result;
}
import { asyncSafeMethod } from …;

asyncSafeMethod(123);