异步调用是否始终创建新线程?
示例:
如果JavaScript是单线程的,那么它如何进行异步回发?它是否实际阻塞,直到它得到回调?如果是这样,这真的是异步电话吗?
答案 0 :(得分:52)
这是一个有趣的问题。
异步编程是编程的范例,主要是单线程,即“跟随一个连续执行的线程”。
您指的是javascript,因此我们在Web浏览器的环境中讨论该语言。 Web浏览器在每个窗口中运行单个javascript执行线程,它处理事件(例如onclick =“someFunction()”)和网络连接(例如xmlhttprequest调用)。
<script>
function performRequest() {
xmlhttp.open("GET", "someurl", true);
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4) {
alert(xmlhttp.responseText);
}
}
xmlhttp.send(sometext);
}
</script>
<span onclick="performRequest()">perform request</span>
(这是一个非工作的例子,仅用于演示概念)。
为了以异步方式完成所有操作,控制线程具有所谓的“主循环”。主循环看起来像这样:
while (true) {
event = nextEvent(all_event_sources);
handler = findEventHandler(event);
handler(event);
}
重要的是要注意,这不是一个“忙碌的循环”。这有点像睡觉的线程,等待活动发生。可以从用户输入活动(鼠标移动,按钮单击,键入),也可以是网络活动(来自服务器的响应)。
所以在上面的例子中,
值得注意的是,alert()是一个阻塞对话框。该对话框启动时,无法处理其他事件。这是网页javascript模型的一个怪癖,我们有一个现成的方法,可以阻止在该页面的上下文中进一步执行。
答案 1 :(得分:16)
Javascript模型是单线程。异步调用不新线程,而是中断现有线程。它类似于内核中的中断。
是的,使用单个线程进行异步调用是有意义的。以下是如何考虑它:当您在单个线程中调用函数时,当前方法的状态被推送到堆栈(即局部变量)。子程序被调用并最终返回,此时原始状态从堆栈中弹出。
通过异步回调,同样的事情发生了!不同之处在于子程序是由系统调用的,而不是由调用子程序的当前代码调用的。
答案 2 :(得分:6)
特别注意JavaScript:
默认情况下, XMLHttpRequest
是非阻止的。在将请求中继到底层网络堆栈后,send()
方法立即返回。来自服务器的响应将安排在事件循环上调用您的回调,正如其他优秀答案所讨论的那样。
这不需要新线程。底层套接字API是可选择的,类似于Java中的java.nio.channels
。
通过将XMLHttpRequest
作为第三个参数传递给open()
,可以构建同步 false
对象。这将导致send()
方法阻塞,直到从服务器收到响应,从而使事件循环受到网络延迟的影响,并可能挂起浏览器直到网络超时。这是Bad Thing™。
Firefox 3.5将引入具有Worker
类的诚实的多线程JavaScript。后台代码在完全独立的环境中运行,并通过在事件循环上调度回调来与浏览器窗口通信。
答案 3 :(得分:4)
在许多GUI应用程序中,异步调用(如Java的invokeLater)只是将Runnable对象添加到其GUI线程队列中。已创建GUI线程,并且它不会创建新线程。但是异步系统甚至不严格要求线程。以libevent为例,它使用select / poll / kqueue等来对套接字进行非阻塞调用,然后完全没有线程地激活对代码的回调。
答案 4 :(得分:2)
不,但涉及多个线程。
异步调用可能会启动另一个线程来完成工作,或者它可能会将消息发布到另一个已经运行的线程的队列中。调用者继续,被调用者在处理消息后回调。
如果您想在此上下文中执行同步调用,则需要发布消息并主动等待回调发生。
总结:将涉及多个线程,但它不一定会创建新线程。
答案 5 :(得分:2)
我不了解javascript,但是例如在Windows Forms世界中,可以在没有多个线程的情况下进行异步调用。这与Windows消息泵的运行方式有关。基本上,Windows窗体应用程序设置一个消息队列,Windows通过该队列放置消息来通知事件。例如,如果移动鼠标,则会将消息放在该队列上。 Windows窗体应用程序将处于无限循环中,消耗所有抛出的消息。根据每个消息包含的内容,它将移动窗口,重新绘制它们甚至调用用户定义的方法等。代表们会识别对方法的调用。当应用程序在队列中找到委托实例时,它会愉快地调用委托引用的方法。
因此,如果您在某个方法中执行某些操作并希望在不创建新线程的情况下生成某些异步工作,那么您所要做的就是使用Control.BeginInvoke方法将委托实例放入队列中。现在,这实际上并不是多线程的,但是如果你向队列中抛出非常小的工作,它看起来就像是多线程的。另一方面,如果你给它一个耗时的执行方法,应用程序将冻结,直到方法完成,这看起来像一个卡住的应用程序,即使它正在做某事。