javascript 为什么事件循环有一个或多个任务队列?

bvjveswy  于 2023-03-16  发布在  Java
关注(0)|答案(2)|浏览(109)

事件循环具有一个或多个任务队列。
根据本说明书后面所述:
任务封装了负责以下工作的算法:
活动:
正在解析:...
回调:...
使用资源:...
对DOM操作的React:...
形式上,任务是一个结构体,它具有:
步骤:
资料来源:
我可以认为每个事件循环都有一个或多个任务队列,那么author有很多操作(Events, Callbacks...),这些操作将被放入相应的任务队列中。
如下图。

根据其源字段,每个任务都定义为来自特定任务源。对于每个事件循环,每个任务源都必须与特定任务队列关联。
例如,用户代理可以有一个用于鼠标和按键事件的任务队列(与用户交互任务源相关联的),以及**与所有其它任务源相关联的另一个。**然后,使用在事件循环处理模型的初始步骤中授予的自由度,它可以在四分之三的时间给予键盘和鼠标事件优先于其它任务的优先权,保持接口响应,但不使其它任务队列饥饿。2注意,在这个设置中,处理模型仍然强制用户代理决不会无序地处理来自任何一个任务源的事件。

nhn9ugyo

nhn9ugyo1#

Javascript是单线程的,它有一个事件循环,其中有一个主任务队列。
浏览器所做的一切都由这个队列处理。
下面是对事件循环的一个很好的解释:https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop
你也可以将 * 微任务 * 排队,在下一个任务退出时运行,引入一个辅助任务队列。这个队列在继续主队列之前会被清空。它本质上是主队列中的一个内部队列,而不是一个并行队列。
下面是对微任务的解释:https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide
如果您感兴趣,下面是对事件循环、任务和微任务的深入解释:https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide/In_depth

jum4pzuy

jum4pzuy2#

队列的概念只是为了让人们能够以普通人熟悉的方式来描述架构。在代码中,没有实际的“队列”。因为任务实际上并不排队,所以它们被列出。
在代码中,你实际拥有的是一个任务组、集合或集合(实现为一个任务数组或任务链表......基本上是一个任务数据库),而真正排队的是事件。
基本的程序流程实际上很容易理解,伪代码如下所示:

while(still_waiting_for(events)) {

  execute_script()

  current_event = wait_for(events)
  current_task = search_related_task(tasks, current_event)
  call_function(current_task.callback)

}

在上面的代码中,没有变量或数据结构来表示事件队列。相反,我们只有一个变量来存储我们正在监听/等待的所有事件的列表。实际的事件“队列”通常由操作系统管理,在我们的代码中是不可见的。
变量tasks是所有等待各自事件的任务。就像我前面提到的,这通常是一组任务。我们称之为任务队列,但正如你所看到的,任务并没有排队等待事件。相反,当事件发生时,我们在tasks中搜索相应的任务。
现在,不同类型任务的数据结构可能略有不同(例如,定时器任务的处理方式通常非常不同)。因此,由于C/C++是一种类型化语言,处理这一事实的一种有效方法是拥有多个任务列表(任务队列)。另一个原因可能是,您可能希望在等待任何事件之前**评估某些类型的任务。最明显的是setImmediate()的回调,在这种情况下,setImmediate回调有一个单独的任务队列,要在其他任务之前处理:

while(still_waiting_for(events)) {

  execute_script()

  for (i=0; i<immediate_tasks.length; i++) {
    immediate = immediate_tasks[i];
    call_function(immediate.callback)
  }

  current_event = wait_for(events)
  current_task = search_related_task(tasks, current_event)
  call_function(current_task.callback)

}

在C/C++中实现这一点的实际代码对于典型的C程序员来说可能有点混乱,但是对于习惯于在JavaScript中异步思考的人来说应该很容易理解。处理异步事件的低级函数根据您使用的操作系统而有所不同(例如,Linux上的poll/epoll、Windows上的重叠I/O、BSD/Mac OS上的kqueue),不同的JavaScript解释器以不同的方式处理它们(例如Node.js中的libuv,它本身使用epoll或kqueue或重叠I/O,具体取决于您编译Node.js的操作系统),但它们的基本工作方式是相似的。
我将使用跨平台(POSIX)select()函数来演示wait_for(events)部分在真实的代码中的实际工作方式:

while(1) {
  retval = select(maxNumberOfIO, readableIOList, writableIOList, NULL, timeout);

  if (retval == -1) {
    perror("Invalid select()");
  }
  else if (retval) {
    //  Find which file descriptor triggered the event
    for (i=0; i<maxNumberOfIO; i++) {
      if (FD_ISSET(i, readableIOList) && readable_task_queue[i]) {
        call_js_callback(readable_task_queue[i]);
      }
      if (FD_ISSET(i, writableIOList) && writable_task_queue[i]) {
        call_js_callback(writable_task_queue[i]);
      }
    }
  }
}

对于select()系统调用,如果你传入0作为timeout的值,它将永远等待一个事件的发生,这允许你设置一个等待时间限制,以防你需要执行其他代码(例如,等待键盘或鼠标事件,该事件可能不使用select()系统调用与您的进程通信)。此超时还允许我们以简单的方式实现setTimeoutsetInterval等定时器事件:

while(1) {
  // Calculate value of timeout:
  nearest_timer = find_nearest_timer(timer_task_queue);
  
  gettimeofday(&current_time, NULL);

  now_millisecs = current_time.tv_sec*1000 + current_time.tv_usec/1000;

  next_timeout_millisecs = nearest_timer.timeout - now_millisecs;
  if (next_timeout_millisecs < 0) {
    next_timeout_millisecs = 1; // remember, zero means wait forever
  }

  timeout.time_t = next_timeout_millisecs/1000;
  timeout.suseconds_t = (next_timeout_millisecs%1000)*1000;

  // Wait for events:
  retval = select(maxNumberOfIO, readableIOList, writableIOList, NULL, timeout);

  // Process event:
  if (retval == -1) {
    perror("Invalid select()");
  }
  else if (retval) {
    //  Find which file descriptor triggered the event
    for (i=0; i<maxNumberOfIO; i++) {
      if (FD_ISSET(i, readableIOList) && readable_task_queue[i]) {
        call_js_callback(readable_task_queue[i]);
      }
      if (FD_ISSET(i, writableIOList) && writable_task_queue[i]) {
        call_js_callback(writable_task_queue[i]);
      }
    }
  }
  else {
    // If we reach here it means we've timed out.
    // So call the timer callback:

    call_js_callback(nearest_timer);
  }
}

如您所见,由于计时器的工作方式与普通I/O事件不同,因此我们使用单独的计时器事件队列。
为了更好地熟悉逻辑流,您可以尝试使用select()系统调用自己在C/C++中实现一个简单的单线程服务器。我已经做过几次了。第一次是作为大学的家庭作业,还有一次是在我负责向Ferite编程语言添加异步I/O时。

相关问题