补篇:协程(Coroutine)实现异步IO操作

2024-06-09 1154阅读

异步IO的概念

异步IO是一种非阻塞的数据读写方法,异步IO与同步IO相对。 当一个异步过程调用发出后,调用者不能立刻得到结果。 实际的IO处理部件在完成操作后,会通过状态、通知或回调机制来通知调用者。

补篇:协程(Coroutine)实现异步IO操作
(图片来源网络,侵删)

在一个CPU密集型的应用中,有一些需要处理的数据可能放在磁盘上。预先知道这些数 据的位置,所以预先发起异步IO读请求。等到真正需要用到这些数据的时候,再等待异步IO完成后获取数据。这种方式使用了异步IO允许程序在等待IO操作完成的同时继续执行其他任务,从而提高了系统的整体效率。

异步IO将比特分成小组进行传送,小组可以是8位的1个字符或更长。发送方可以在任何时刻发送这些比特组,而接收方从不知道它们会在什么时候到达。

异步传输存在一个潜在的问题,即接收方并不知道数据会在什么时候到达。在它检测到数据并做出响应之前,第一个比特已经过去了。这就像有人出乎意料地从后面走上来跟你说话,而你没来得及反应过来,漏掉了最前面的几个词。因此,每次异步传输的信息都以一个起始位开头,它通知接收方数据已经到达了,这就给了接收方响应、接收和缓存数据比特的时间;在传输结束时,一个停止位表示该次传输信息的终止。按照惯例,空闲(没有传送数据)的线路实际携带着一个代表二进制1的信号,异步传输的开始位使信号变成0,其他的比特位使信号随传输的数据信息而变化。最后,停止位使信号重新变回1,该信号一直保持到下一个开始位到达。例如在键盘上数字“1”,按照8比特位的扩展ASCII编码,将发送“00110001”,同时需要在8比特位的前面加一个起始位,后面一个停止位。

Android 实现异步IO操作:

在Android开发中,异步IO通常用于执行耗时的网络请求、文件读写等操作,避免UI线程被长时间运行的IO耗时任务阻塞(避免阻塞UI线程,保持应用的响应性)。但是,在使用异步IO时,也需要注意线程安全和资源管理的问题,确保不会出现内存泄漏或竞态条件等问题。

Android提供了多种机制来实现异步IO操作:使用AsyncTask,Thread,HandlerThread,IntentService,JobIntentService,RxJava等机制实现异步IO操作.它们本质上都是对线程或线程池的封装,该类可以将耗时的操作放在后台线程池来处理,而不需要人为地另开线程来处理。

使用AsyncTask实现异步IO操作:

这是一个轻量级的异步类,适合在后台执行简单的异步任务,如网络请求或文件读写。它包含doInBackground方法在后台线程执行耗时操作,onProgressUpdate在UI线程更新进度,以及onPostExecute在任务完成后在UI线程执行后续操作。使用AsyncTask来实现异步IO的优点就是简单便捷,各个过程都有明确的回调,过程可控。

使用HandlerThread实现异步IO操作:

HandlerThread是一种在单独的线程中处理消息和Runnable对象的机制。通过使用Handler与HandlerThread结合,可以在后台线程中处理耗时的操作,并通过Handler将结果发送回UI线程进行更新。

使用IntentService实现异步IO操作:

IntentService是一种在后台执行异步任务的服务。它会在一个单独的工作线程中处理传入的Intent,并自动处理线程间的通信。IntentService适用于执行不需要立即返回结果的长时间运行的操作。

使用JobIntentService实现异步IO操作:

在Android O及更高版本中,建议使用JobIntentService作为IntentService的替代方案。它提供了更多的灵活性和更好的电池效率。

使用RxJava实现异步IO操作:

RxJava是一个在Java VM上使用可观察序列来组成异步和基于事件的程序的库。它提供了一种声明式的方式来处理异步数据流。

Handler

 在Android中,Handler本身并不直接实现异步IO操作。

可以在后台子线程中执行IO操作(耗时任务), 然后通过Handler将数据结果,发送传递 给UI主线程进行更新,从而间接地支持异步IO的实现。

例子:使用Handler结合Thread实现异步IO:

1.我们首先初始化了一个Handler对象,它绑定到主线程的Looper上,这样我们就可以在主线程中执行Runnable对象。

2.然后,我们创建并启动了一个新的Thread来执行耗时的IO操作。

3.在后台线程中,我们调用performIOOperation()方法模拟了一个耗时的IO操作。

4.一旦IO操作完成,我们使用Handler的post方法将一个Runnable对象发送回主线程。这个Runnable对象包含了更新UI或处理IO操作结果的逻辑。

5.最后,在updateUIWithResult()方法中,我们执行了必须在主线程中完成的UI更新操作。

public class AsyncIOActivity extends AppCompatActivity {
    private static final String TAG = "AsyncIOActivity";
    private Handler mainHandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_async_io);
        // 初始化主线程的Handler
        mainHandler = new Handler(Looper.getMainLooper());
        // 启动后台线程执行IO操作
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 模拟耗时IO操作,比如网络请求或文件读写
                final String result = performIOOperation();
                // 使用Handler将结果发送回主线程
                mainHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        // 在主线程中更新UI或处理结果
                        updateUIWithResult(result);
                    }
                });
            }
        }).start();
    }
    private String performIOOperation() {
        // 在这里执行实际的IO操作,例如网络请求或文件读写
        // 这个操作是耗时的,因此应该在后台线程中执行
        // 模拟耗时操作
        try {
            Thread.sleep(2000); // 假设耗时2秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "IO操作完成";
    }
    private void updateUIWithResult(String result) {
        // 在这里更新UI或处理从IO操作中获取的结果
        // 这个操作必须在主线程中执行,因为Android不允许在非主线程中更新UI
        Log.d(TAG, "UI updated with result: " + result);
        // 例如:textView.setText(result);
    }
}

 在主线程也可以通过 Handler发消息给子线程,不过在子线程接收数据,需要轮训Handler,需要初始化Looper.prepare()和Looper.loop()。

public class LooperThreadActivity extends Activity {
 
    private Handler mHandler = null;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        new MyThread().start();
        //发消息到目标子线程
        mHandler.obtainMessage(0).sendToTarget();
 
    }
 
    class MyThread extends Thread{
 
        @Override
        public void run() {
            super.run();
            //1.建立消息循环,初始化Looper
            Looper.prepare();
            mHandler = new Handler(){
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    int what = msg.what;
                    if(what == 0){
                        //
                    }
                }
            };
            //启动消息循环
            Looper.loop();
        }
    }
}

使用协程(Coroutine)实现异步IO操作

Kotlin 协程(Coroutine)是一种轻量级的线程,它允许我们以同步的方式编写异步代码,从而极大地简化了异步编程的复杂性。协程不是真正的线程,它们是在单个线程上通过协作调度来执行的,这避免了线程切换的开销,提高了效率。下面将详细讲解 Kotlin 使用协程实现异步 IO 的原理和流程。

使用协程实现异步 IO 的原理

1.挂起函数(Suspend Function):

Kotlin 协程的核心是suspend挂起函数。这些suspend挂起函数可以在执行过程中暂停(挂起),等待某个异步任务(IO操作,网络请求)完成后再恢复执行。这使得协程能够在不阻塞主线程的情况下执行耗时的操作。

  挂起函数(Suspend Functions)在 Kotlin 协程中扮演着至关重要的角色,它们是实现异步 IO 的  关键。 

 挂起函数 原理:

挂起函数必须在一个协程上下文中执行,通常是通过 launch、async 等协程构建器创建的。此外,挂起函数只能被其他挂起函数或协程调用,以确保正确的执行顺序和线程安全性。挂起函数通过非阻塞性和协作式调度的原理,实现了在 Kotlin 协程中执行异步 IO 操作的高效流程。它们允许代码以同步的方式编写,而实际上却是在底层执行异步操作,从而简化了异步编程的复杂性。

❶非阻塞性:挂起函数允许协程在遇到耗时的操作时(IO 操作), 而不会阻塞当前(主)线程。这使得(主)线程可以继续执行其他任务,从而提高了整体的并发性能。

❷协作式调度:协程不是由操作系统来调度的,而是由用户代码(或者库代码)来显式地调度和挂起。挂起函数是这种协作式调度的关键部分。当这个挂起函数被调用执行时,它会暂停当前协程的执行,并释放当前(主)线程的控制权。当异步操作完成或条件满足时,协程会恢复执行,恢复对当前(主)线程的控制权。

❸轻量级:由于挂起函数并不涉及线程的切换,因此它们相比于传统的线程或回调机制更加轻量级。这减少了系统资源的消耗,提高了程序的性能。

2.协程构建器(Coroutine Builder):

Kotlin 标准库提供了多个协程构建器,如 launch、async 和 runBlocking 等。这些构建器用于创建协程并启动它们的执行。

3.调度器(Dispatcher):

协程的调度器决定了协程在哪个线程或线程池上执行任务。如果你要更新UI,就让调度器把协程调度到UI主线程Dispatchers.Main(或许默认就在UI主线程);如果你要做异步IO耗时任务,就让调度器把协程调度到IO子线程Dispatchers.IO。

 Kotlin 提供了默认的调度器,如 Dispatchers.Default(用于计算密集型任务)、Dispatchers.IO(用于 IO 密集型任务)和 Dispatchers.Main(用于在 Android 的主线程上执行 UI 更新)。

4.延续(Continuation):

协程的挂起和恢复是通过 Continuation 机制实现的。

当一个挂起函数被调用时,它会将当前的执行状态保存到一个 Continuation 对象中,并释放当前线程的控制权(不在当前线程里了)。

当异步操作完成时,它会恢复该 Continuation,从而使挂起函数继续执行,重新掌控当前线程的控制权(又回到当前线程里了)。

使用协程实现异步 IO 的流程

以下是一个使用 Kotlin 协程实现异步 IO 的典型流程:

1.添加依赖:

 首先,你需要在项目的 build.gradle 文件中添加 Kotlin 协程的依赖。

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:" // 对于 Android 项目

2.创建一个协程:

使用协程构建器(如 GlobalScope.launch 或 viewModelScope.launch 对于 Android ViewModel)来创建协程。

//协程调度器调度到IO子线程
GlobalScope.launch(Dispatchers.IO) {
    // 在这里执行异步 IO 操作
}

3.调用挂起函数:

在协程中,你可以调用挂起函数来执行异步 IO 操作。这些挂起函数通常是异步 API 的封装,它们使用协程的挂起和恢复机制来处理异步性。

使用挂起函数withContext,   指定一个不同的调度器(Dispatchers.IO )调度到IO子线程里,在代码块里执行异步IO操作。

释放对当前主线程的控制权(不在当前主线程里了)。 

/**
使用挂起函数withContext,   指定一个不同的调度器(Dispatchers.IO )调度到IO子线程里,在代码块里执行异步IO操作
*/
val result = withContext(Dispatchers.IO) {
    // 执行网络请求或文件读写等异步操作
    // 返回操作结果
}

4.挂起协程:

当挂起函数被调用时,它会检查当前是否处于可以立即执行的状态。如果不能立即执行(比如需要等待网络响应),挂起函数就会暂停协程的执行,并释放当前(主)线程的控制权。此时,协程的状态会被保存,以便稍后恢复。

5.执行异步 IO操作

挂起函数withContext内部会开始执行异步IO操作。这通常涉及到底层库或框架的异步 API,比如使用 kotlinx.coroutines 提供的网络请求库。这些 API 在后台线程上执行实际的 IO 操作,而不会阻塞主线程。

6.恢复协程:

当异步操作完成(比如网络请求返回了结果),挂起函数withContext会接收到通知。此时,它会检查之前挂起的协程是否还存活,并尝试恢复该协程的执行。恢复过程包括重新获取对(主)线程的控制权,并从上一次挂起的位置继续执行代码。

7.更新 UI:

 一旦协程恢复执行,挂起函数withContext会返回异步IO操作的结果(或异常)。你可以在协程中处理这个结果,比如更新 UI 或进行进一步的计算。

如果需要在 UI 线程上更新数据结果,你可以使用 withContext(Dispatchers.Main),指定一个不同的调度器(Dispatchers.Main )调度到Main线程里(切换到UI主线程),在代码块里执行更新数据结果  。

//返回数据结果result 
val result=withContext(Dispatchers.Main) {
    // 更新 UI,如设置文本、显示图像等
}

 在协程中,你可以使用 try-catch 块来处理可能出现的异常。

try {
     //返回数据结果result 
    val result = withContext(Dispatchers.IO) {
      // 更新 UI,如设置文本、显示图像等
        // 执行可能抛出异常的异步操作
    }
} catch (e: Exception) {
    // 处理异常
}

 8.取消协程:

如果需要取消协程的执行,你可以使用 Job 对象来管理协程的生命周期。launch 和 async 构建器都会返回一个 Job 对象,你可以调用它的 cancel 方法来取消协程。

val job = GlobalScope.launch {
    // 协程代码
}
// 在某个时刻取消协程
job.cancel()
VPS购买请点击我

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

目录[+]