异步代码是不是在 UI 线程或新/不同线程中运行以不阻塞 UI?

Posted

技术标签:

【中文标题】异步代码是不是在 UI 线程或新/不同线程中运行以不阻塞 UI?【英文标题】:Does Asynchronous code run in the UI thread or a new/different thread to not block the UI?异步代码是否在 UI 线程或新/不同线程中运行以不阻塞 UI? 【发布时间】:2020-09-12 21:35:43 【问题描述】:

虽然这个问题似乎已经被问过多次并且已经得到了高度评​​价的答案,但我想表明多个答案是相互矛盾的,我永远无法完全理解异步代码的内部结构。我完全理解这意味着继续顺序代码执行并稍后完成任务,我正在尝试理解稍后部分。

答案 1 - 建议 UI/Main

首先,this question 内部包含以下测试,表明异步代码在 Main/Ui 线程上运行并链接 article 解释为什么没有其他线程用于异步代码。

问:“在我看来,由于我主要做 UI 开发,所以异步代码是不在 UI 线程上运行的代码,而是在其他线程上运行的代码。”

答:这种信念很普遍,但却是错误的。不要求异步代码在任何第二个线程上运行。

事实上的答案 建议使用线程进行异步编程是“错误的”,说 “线程是工作人员。异步工作流程可以发生在一个线程上。异步工作流程的重点是避免雇用更多工作人员,如果可以避免的话。”

答案 2 - 建议 UI/Main

我读到的下一个问题也建议异步代码运行在 Main/UI 线程上,however the comparison used 是 javascript,我们都知道它是一种单线程语言。例如,假设我运行这段代码,

 function wait(ms) 
  var start = Date.now(),
      now = start;
  while (now - start < ms) 
    now = Date.now();
  

setTimeout(() => 
    wait(5000); 
, 3000)

setTimeout 将被异步调用,在3000ms 之后回调将被添加到Event Loop 并最终运行。然而,方法执行将在 Main/UI 线程上完成,因此导致 UI 被冻结为5000ms

答案 3 - 建议新线程

This answer 建议在他说的答案中将异步代码运行到一个新线程中。 “当您异步执行某项操作时,您可以在它完成之前继续执行另一个任务。话虽如此,在计算机的上下文中,这转化为在另一个“线程”上执行进程或任务。

答案 4 - 建议新话题

The final answer 建议异步代码是基于线程的,通过说,“在一般情况下,异步调用不一定会创建新线程。这是实现它的一种方法,使用预先存在的线程池或者外部进程是其他方式。它在很大程度上取决于语言、对象模型(如果有的话)和运行时环境。异步只是意味着调用线程不会坐下来等待响应,也不会在调用中发生异步活动线程。”,重点是“也不会在调用线程中发生异步活动”

我不确定从这一点开始,根据我的理解,我认为异步代码必须在不同的线程中执行,永远不要阻塞 UI,我想唯一的例外是 JavaScript。但是,即使涉及在单线程语言上执行异步代码,我也会认为运行的任何函数回调都必须在运行另一个回调之前运行完成。

The second answer on here 通过绘图表明,当异步代码在单个线程上运行时,它会停止并运行不同的异步回调,来回切换,就像线程完成之前一样。这对我来说并不完全有意义,因为对于异步代码,它们通常包含一个回调以防止任何类型的竞争条件,假设 B 需要 A 首先运行完成,B 将是 A 内部的回调。回调 A和 B 不会“一起跑”并在彼此之间切换。

【问题讨论】:

I would think Asynchronous code must execute in a different thread - 没有。 ***.com/q/17661428/11683,blog.stephencleary.com/2013/11/there-is-no-thread.html。您引用的问题中的一些人将线程与任务混淆,甚至将它们视为同一事物,而异步与线程无关。 when Asynchronous code is ran on a single thread, it stops and runs a different Asynchronous callback, switching back and forth - ***.com/q/37419572/11683 问题“Does Asynchronous code run in the UI thread or a new/different thread to not block the UI?”取决于哪种语言的实现。对于 C#、Javascript 和“概念理论”,您会期望(可能)有不同的答案。 【参考方案1】:

让我们从“低级”向上工作。

对于低级硬件,大多数现代设备(网络、磁盘)使用总线主控或 DMA 进行与计算机主内存之间的传输,并在传输完成时生成一个 IRQ;并支持“驱动程序向设备发出命令,然后设备在命令完成时发出 IRQ”模型,用于不涉及 IO 的事情。举个例子;如果您想(例如)从单 CPU 计算机上的磁盘异步读取一些数据; CPU(设备驱动程序)可以告诉磁盘控制器硬件要做什么,然后 CPU 可以在磁盘控制器传输数据的同时继续做有用的工作,然后当 IRQ 到达时(表示传输完成)它可以触发某种“异步传输完成”动作。

如果设备不支持这一点(例如,必须使用某种“编程 IO”,其中 CPU 需要用于传输数据等),则必须以某种方式模拟异步性 - 通过使用另一个 CPU,通过撒谎(同步执行并假装“异步完成”立即发生),或者使用单个 CPU 在不同工作之间快速切换(一个请求异步操作,另一个正在执行异步操作)来制造假象异步性。

下一层是操作系统的内核和设备驱动程序。对于几乎所有现代操作系统,所有需要足够时间才值得关心的事情都是(内部)完全异步的(即使底层硬件不支持它,也会模拟异步操作);主要是因为多处理和性能(例如,确保来自不同进程的单独请求能够让所有硬件设备在可能的情况下并行执行有用的工作,通常具有 IO 优先级以尝试确保更重要的工作比不重要的工作更快地发生,通常与后台发生的各种预取策略相结合,以增加在任何进程请求之前完成工作的机会)。然而;提供给用户空间的 API 可能不会向用户空间进程公开内核对异步操作的支持。例如,最初的 POSIX API 根本不支持异步操作(它在为时已晚后被追溯入侵,这就是为什么你甚至不能做一些基本的事情,比如异步打开文件)。

下一层是语言的运行时。这介于语言标准(可能会或可能不会迎合异步操作,可能会或可能不会有某种“用户空间线程/光纤/任何东西”)和内核的 API(可能会或可能不会提供一些/任何异步操作)。对于语言的运行时支持异步操作但底层内核 API 不支持的情况,必须以某种方式对其进行模拟 - 例如使用(一个,更多?)“内核线程”(并可能将其隐藏在任何类型的“用户空间线程/光纤/任何东西”后面),或通过撒谎(同步执行并假装“异步完成”立即发生)。

最后一层是第三方库/模块/包,用于在最初不支持它们的语言之上添加异步操作。这总是涉及某种模拟。

请注意,如果您查看整个层集,整个系统可能会在 2 个不同级别(在内核/设备驱动程序中,以及在语言运行时的用户空间或在它的上面);因此可以同时使用 2 种不同的模拟异步性的方法(即使在这种情况下,也可能不会使用线程/任务来模拟/提供异步性)。

当然,如果有 100 种语言,每种语言平均有 4 种实现;那么将有 400 种排列,它们对于异步操作的实际实现方式可能都有不同的答案;因此,您可以期望(可能)对 C#、Javascript 等给出不同的答案……这可能是正确的(对于该语言的实现),尽管是错误的(对于该语言的其他实现或其他语言)。

【讨论】:

以上是关于异步代码是不是在 UI 线程或新/不同线程中运行以不阻塞 UI?的主要内容,如果未能解决你的问题,请参考以下文章

C# 中的线程安全异步代码

如何通过 AsyncTask 类或新线程进行游标查询? [复制]

如果我在不同的线程上同步运行,它是不是会使其总体异步?

iOS子线程操作UI

Android:异步处理之Handler+Thread的应用

从异步线程访问 UI 线程