【问题标题】:Awaiting asynchronous function blocks main thread等待异步函数阻塞主线程
【发布时间】:2021-12-12 09:21:37
【问题描述】:

我正在用 JavaScript 构建一个不和谐的音乐机器人(我昨天刚开始学习,所以代码可能有点乱)。到目前为止,我已经将歌曲添加到队列中,播放歌曲,然后在当前歌曲完成后移至下一首歌曲。但是,我遇到了一个小问题:每次我将一首歌曲添加到队列中时,机器人的音乐都会暂停一秒钟,而一首歌曲已经在播放。我发现这是因为 yt-search,这是我用来在 youtube 上搜索特定视频的模块。

这是 main.js 中的相关代码,通过 Node 运行的实际文件。

    else if (cmd === 'play') {
    const connection = getVoiceConnection(msg.guildId);

    if (!connection) {
        const join = await client.modules.backend.join(msg);

        if (!join) return console.log('Unable to join');
    }

    if (!qMap.get(msg.guildId)) {
        console.log('No queue for this server, creating a new one');

        await client.modules.queue.construct(qMap, msg.guildId, msg.member.voice.channel, connection);
    }

    client.modules.queue.add(msg, qMap.get(msg.guildId), args, player);

有人使用“播放”命令播放歌曲;他们的搜索词与其他一些变量一起作为 args 传递给 queue.addqueue.add 是从另一个模块导入的;这是代码:

async function queueAddSong(msg, queue, terms, player) {

song = searchForVideo(msg, terms);

console.log(`Adding to queue: ${song.title}`);

queue.songs.push(song);

if (player.state.status === 'idle') startQueue(queue, player);
}

这里是searchForVideo(),它会进行实际搜索:

async function searchForVideo(msg, query) {

let song = {};

// Was a valid URL passed? If yes, use ytdl. Otherwise, search.
if (ytdl.validateURL(query[0])) {
    songInfo = await ytdl.getInfo(query[0]);

    song = {
        title: songInfo.videoDetails.title,
        url: songInfo.videoDetails.video_url
    };
} else {
    // Search
    const vidResults = await ytSearch(query.join(' '));

    if (vidResults.videos.length > 1) {
        song = {
            title: vidResults.videos[0].title,
            url: vidResults.videos[0].url
        };
    } else {
        msg.reply(':x: No results found');
        return null;
    }
}

return song;
}

这里是startQueue(),它将歌曲转换为可用的流并播放它

function startQueue(queue, player) {
console.log(`Starting a new queue with song: ${queue.songs[0].title}`);
// Start a queue. Behavior is different from just switching songs, so I've made this a separate function.

const connection = getVoiceConnection(queue.voiceChannel.guildId);
const stream = ytdl(queue.songs[0].url, {
    filter: 'audioonly'
});
const resource = createAudioResource(stream, {
    inputType: StreamType.Arbitrary
});

// Play first song
player.play(resource);
connection.subscribe(player);

// Add event listener to play next song
player.on(AudioPlayerStatus.Idle, () => {
    if (queue.songs.length > 1) {
        console.log(`Song finished. Moving to: ${queue.songs[1].title}`);

        queue.songs.shift();
        playInQueue(queue, 0, player);
    } else {
        console.log(`Queue finished.`);
    }
});
}

似乎ytsearch 的强度足以让整个线程停止足够长的时间以引起注意。如何独立于主线程运行搜索,以便无论搜索花费多少时间或处理能力,音乐在搜索时继续播放?

【问题讨论】:

  • 您从引起问题的调用ytsearch() 中获得了多少结果?是不是数据量很大?机器人如何播放音乐?那个代码在哪里?使用正确编写的代码,我认为您不需要将 workerThread 仅用于网络搜索,除非您或库花费大量周期来处理结果。
  • 没那么多;大约15-20。我对ytsearch() 不是很熟悉,所以我不确定每个结果返回多少数据,但它看起来像很多字符串。我实际上错了,不是 await 而是 ytsearch() 的实际处理导致了口吃。我最终用 workerThread 解决了这个问题,我对此很满意,因为这是一个小规模的个人项目。要回答其他所有问题,请使用 discord.js 中的 voiceConnectionaudioResourceaudioPlayer。为简洁起见,我没有包含该代码,但我现在将用它来编辑问题。
  • 似乎song = searchForVideo(msg, terms); 缺少await 关键字?
  • playInQueue 是做什么的?
  • @Bergi 是的,你是对的,那是缺少 awaitplayInQueue()startQueue() 所做的基本相同,但没有添加新的事件侦听器。它只是使用ytdl 用指定的歌曲创建一个新流,用该流创建一个新资源,然后使用player.play(resource)

标签: javascript node.js discord.js


【解决方案1】:

其实我的标题是错的;导致口吃的不是await 部分,而是ytSearch() 函数本身。似乎调用它的强度足以让整个线程停止足够长的时间以引起注意。虽然仅仅搜索 youtube 可能有点过头了,但我设法通过给 searchForVideo() 一个与主线程分开的工作线程来解决这个问题。我还不能接受我自己的答案,所以我暂时把它留在这里。

【讨论】:

    【解决方案2】:

    考虑删除 asyncawait 并用正常的承诺解决方法替换它们。

    function searchForVideo(msg, query) {
      if (ytdl.validateURL(query[0])) {
        return ytdl.getInfo(query[0])
          .then(songInfo => {
            return {
              title: songInfo.videoDetails.title,
              url: songInfo.videoDetails.video_url
            }
          });
      } else {
        return ytSearch(query.join(' '))
          .then(vidResults => {
            if (vidResults.videos.length > 1) {
              return {
                title: vidResults.videos[0].title,
                url: vidResults.videos[0].url
              };
            } else {
              msg.reply(':x: No results found');
              return null;
            }
          });
      }
    }
    然后在您的 queueAddSong 函数中,解决承诺。

    function queueAddSong(msg, queue, terms, player) {
      searchForVideo(msg, terms).then(song => {
        console.log(`Adding to queue: ${song.title}`);
        queue.songs.push(song);
      });
      if (player.state.status === 'idle') startQueue(queue, player);
    }

    【讨论】:

    • async/await 没有任何问题。您认为“正常的承诺解决方法”有什么优势?
    • @Bergi,区别显然是blockingunlbocking。只需尝试运行以下代码。 ` const foo = () => { return Promise.resolve(1) } const fun1 = async () =>{ console.log('在 fun1 中调用 foo 之前') const result = await foo(); console.log({result}, '结果 fun1'); console.log('在 fun1 中调用 foo 之后') } const fun2 = () => { console.log('在 fun2 中调用 foo 之前') foo().then(result => {console.log({result} , '结果 fun2')}); console.log('在 fun2 中调用 foo'); } 乐趣 1();乐趣2(); `
    • @Bergi await 将阻止以下代码,直到 promise 被解决,而 .then 不会。事实是,如果下面的代码依赖于已解析的 promise,那么asyn/await 肯定是一个很好的语法糖,但如果下面的代码与 promise 完全无关,那么使用 .then 将其变为解锁模式有什么问题?
    • 该函数中.then() 之后没有可以“解锁”的代码。您将 await 之后的所有代码放入 then 回调中,因为它应该等待。
    • 不,async function 的调用者被阻止。它只是返回一个承诺。切换到.then() 语法没有任何改变。
    猜你喜欢
    • 1970-01-01
    • 2020-01-17
    • 1970-01-01
    • 2022-01-04
    • 1970-01-01
    • 2016-03-13
    • 2014-08-10
    • 2011-04-03
    • 1970-01-01
    相关资源
    最近更新 更多