簡體   English   中英

使用async / await在JS / TS中的異步有界隊列

[英]Asynchronous Bounded Queue in JS/TS using async/await

我正試圖繞過async/await ,我有以下代碼:

class AsyncQueue<T> {
    queue = Array<T>()
    maxSize = 1

    async enqueue(x: T) {
        if (this.queue.length > this.maxSize) {
            // Block until available
        }

        this.queue.unshift(x)
    }

    async dequeue() {
        if (this.queue.length == 0) {
            // Block until available
        }

        return this.queue.pop()!
    }
}

async function produce<T>(q: AsyncQueue, x: T) {
    await q.enqueue(x)
}

async function consume<T>(q: AsyncQueue): T {
    return await q.dequeue()
}

// Expecting 3 4 in the console
(async () => {
    const q = new AsyncQueue<number>()
    consume(q).then(console.log)
    consume(q).then(console.log)
    produce(q, 3)
    produce(q, 4)
    consume(q).then(console.log)
    consume(q).then(console.log)
})()

當然,我的問題在於代碼中的“阻塞直到可用”部分。 我希望能夠“停止”執行直到發生某些事情(例如,出列暫停直到出現入隊,反之亦然,考慮到可用空間)。 我覺得我可能需要使用協同程序,但我真的想確保我在這里沒有錯過任何async/await魔法。

17/04/2019更新:長話短說,下面的AsyncSemaphore實現中有一個錯誤,它是使用基於屬性的測試捕獲的。 你可以在這里閱讀關於這個“故事”的所有內容 這是固定版本:

class AsyncSemaphore {
    private promises = Array<() => void>()

    constructor(private permits: number) {}

    signal() {
        this.permits += 1
        if (this.promises.length > 0) this.promises.pop()!()
    }

    async wait() {
        this.permits -= 1
        if (this.permits < 0 || this.promises.length > 0)
            await new Promise(r => this.promises.unshift(r))
    }
}

最后,經過相當大的努力,受@Titian答案的啟發,我想我解決了這個問題。 代碼中充滿了調試消息,但它可能有助於控制流程的教學目的:

class AsyncQueue<T> {
    waitingEnqueue = new Array<() => void>()
    waitingDequeue = new Array<() => void>()
    enqueuePointer = 0
    dequeuePointer = 0
    queue = Array<T>()
    maxSize = 1
    trace = 0

    async enqueue(x: T) {
        this.trace += 1
        const localTrace = this.trace

        if ((this.queue.length + 1) > this.maxSize || this.waitingDequeue.length > 0) {
            console.debug(`[${localTrace}] Producer Waiting`)
            this.dequeuePointer += 1
            await new Promise(r => this.waitingDequeue.unshift(r))
            this.waitingDequeue.pop()
            console.debug(`[${localTrace}] Producer Ready`)
        }

        this.queue.unshift(x)
        console.debug(`[${localTrace}] Enqueueing ${x} Queue is now [${this.queue.join(', ')}]`)

        if (this.enqueuePointer > 0) {
            console.debug(`[${localTrace}] Notify Consumer`)
            this.waitingEnqueue[this.enqueuePointer-1]()
            this.enqueuePointer -= 1
        }
    }

    async dequeue() {
        this.trace += 1
        const localTrace = this.trace

        console.debug(`[${localTrace}] Queue length before pop: ${this.queue.length}`)

        if (this.queue.length == 0 || this.waitingEnqueue.length > 0) {
            console.debug(`[${localTrace}] Consumer Waiting`)
            this.enqueuePointer += 1
            await new Promise(r => this.waitingEnqueue.unshift(r))
            this.waitingEnqueue.pop()
            console.debug(`[${localTrace}] Consumer Ready`)
        }

        const x = this.queue.pop()!
        console.debug(`[${localTrace}] Queue length after pop: ${this.queue.length} Popping ${x}`)

        if (this.dequeuePointer > 0) {
            console.debug(`[${localTrace}] Notify Producer`)
            this.waitingDequeue[this.dequeuePointer - 1]()
            this.dequeuePointer -= 1
        }

        return x
    }
}

更新:這是一個使用AsyncSemaphore的干凈版本,它真正封裝了通常使用並發原語完成的方式,但適用於異步AsyncSemaphore單線程事件循環™樣式的JavaScript與async/await 您可以看到AsyncQueue的邏輯變得更加直觀,並且通過Promises的雙重同步被委托給兩個信號量

class AsyncSemaphore {
    private promises = Array<() => void>()

    constructor(private permits: number) {}

    signal() {
        this.permits += 1
        if (this.promises.length > 0) this.promises.pop()()
    }

    async wait() {
        if (this.permits == 0 || this.promises.length > 0)
            await new Promise(r => this.promises.unshift(r))
        this.permits -= 1
    }
}

class AsyncQueue<T> {
    private queue = Array<T>()
    private waitingEnqueue: AsyncSemaphore
    private waitingDequeue: AsyncSemaphore

    constructor(readonly maxSize: number) {
        this.waitingEnqueue = new AsyncSemaphore(0)
        this.waitingDequeue = new AsyncSemaphore(maxSize)
    }

    async enqueue(x: T) {
        await this.waitingDequeue.wait()
        this.queue.unshift(x)
        this.waitingEnqueue.signal()
    }

    async dequeue() {
        await this.waitingEnqueue.wait()
        this.waitingDequeue.signal()
        return this.queue.pop()!
    }
}

更新2:在上面的代碼中似乎隱藏了一個微妙的錯誤,當嘗試使用大小為0的AsyncQueue時,這一點變得明顯。語義確實有意義:它是一個沒有任何緩沖區的隊列,發布者總是等待一個消費者存在。 阻止它工作的線是:

await this.waitingEnqueue.wait()
this.waitingDequeue.signal()

如果你仔細觀察,你會發現dequeue()enqueue()不完全對稱。 事實上,如果交換這兩個指令的順序:

this.waitingDequeue.signal()
await this.waitingEnqueue.wait()

然后一切再次起作用; 對我來說似乎很直觀,我們發信號表示在實際等待enqueuing發生之前有一些對dequeuing()感興趣的東西。

如果沒有大量的測試,我仍然不確定這不會重新引入細微的錯誤。 我會把這作為挑戰;)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM