协程异常处理机制竟然和事件分发机制一母同胞?

简介: 协程异常处理机制竟然和事件分发机制一母同胞?

1. 前言


如果你是第一次听说有人把异常处理和事件分发联系在一起,相信你会跟我第一次接触协程异常处理机制时一样,一脸懵逼。别说在座的各位有不少Android老司机,就算是Android萌新,也应该知道,异常处理不就是try catch这么简单的事么,怎么能和复杂的事件分发机制扯上关系?别不信还真扯的上关系。


如果你已经接触协程知识有段时间,并且知道协程的异常处理机制和我们以往了解的异常处理机制不一样,但是又没能完全它的原理,我会一步一步带你搞清楚这是怎么回事。

如果你对协程异常处理机制了然于胸,那么恭喜你,我觉得在当前时间点,你已经超越了大部分的人。希望这篇文章可以给处于各个不同阶段的朋友,不同程度的帮助。


当然,本文并不会详细介绍事件分发。Android的View体系和协程的结构化并发一样,它们对应的数据结构都是tree。View的事件分发和协程的Cancel传播机制很相似,使用的都是树的深度遍历算法。事件分发可参考Android事件分发机制分析


2. 异常捕获


  1. Kotlin中的异常捕获和Java一样,用try catch就能捕获住异常。我们来看一个简单的例子👇。通过try catch捕获异常。结果打印"caught null pointer exception"。


640.png

启动一个协程👇。在协程体内捕获异常,结果与预期一样,打印“caught null pointer exception”。

640.png


假设有个第三方提供的方法thirdApi。thirdApi方法启动了一个协程而且没有捕获异常👇。结果导致程序崩溃。


640.png


开始一脸懵逼 要解决上述崩溃问题,我们的第一反应是在调用thirdApi的地方加上try catch👇,so easy嘛!运行代码,傻眼了,程序照样崩溃。try catch包住CoroutineScope.launch无法捕获住异常?


640.png


虽然不明白上述代码为什么捕获不住异常,但是我知道协程有CoroutineExceptionHandler可以处理异常,👇如下试试看能否捕获住异常?结果打印"caught null pointer exception"。


640.png


继续一脸懵逼 👇如果把exceptionHandler放到第二个launch方法中,竟然又捕获不住异常,程序又崩溃了。


640.png


继续一脸懵逼 如果用coroutineScope包住第二个launch方法,情况又不一样了👇,结果打印“caught exception in try catch exception null pointer exception”。异常被try catch捕获住,而不是被exceptionHandler捕获住。


640.png


继续一脸懵逼 如果用supervisorScope代替coroutineScope,情况又不一样了👇,结果打印“caught exception in handler null pointer exception”。被exceptionHandler捕获住了。


640.png


在7的代码基础上,如果把try catch去掉,在外层的launch方法中增加outerExceptionHandler参数👇。打印结果为“caught exception in outer handler null pointer exception”。异常被outerExceptionHandler捕获住了。

640.png


继续一脸懵逼 在9的代码基础上把coroutineScope替换成supervisorScope👇。打印结果为“caught exception in inner handler null pointer exception”。异常被innerExceptionHandler捕获住了。

640.png


上面10个场景,不管你是初学者还是协程老手,相信你已经晕头转向了。一个简单的异常处理,到协程里面怎么就变幻出这么多种场景,而且结果都不一样。如果不掌握协程异常处理逻辑,真不敢在项目中上协程。否则项目怎么崩溃的都不知道。


别纠结上述场景了,那只不过是我罗列出的10个场景,从理论上讲,这些场景可以72变,变化出无数种场景。但是万变不离其宗,只有掌握了原理,方能应对自如。


3. 异常处理原理


聊聊Job和SupervisorJob的区别一文中我简单介绍了异常处理原理。要想理解异常处理原理也必须先了解结构化并发,可以参考Kotlin协程是如何建立结构化并发的?


异常的处理逻辑可以用职场的例子解释。假设职场的潜规则是,任何员工出错了,首要是要向上级报告,如果上级愿意处理你的错误,那员工就不用管了,如果上级将问题打回给员工,那错误就得由员工自己处理。

以下图为例讲解异常处理机制,Job5处发生异常(此处指非CancellationException)。


640.png



  1. Job5首先会把异常传递给Job2处理。Job2有两种选择,选择一是不处理该异常,将异常打回给Job5,Job5调用handleJobException方法。选择二是Job2选择处理该异常,那么异常就传递给Job2了,Job5再也没有机会处理异常。
  2. Job2处理异常的逻辑同Job5,首先会把异常传递给Job0。Job0同样有两种选择,打回交由Job2处理,调用Job2的handleJobException方法或者由Job0处理。
  3. 协程中根Job是不会处理异常的,它会将异常打回交由子Job处理。本例子中Job0会把异常交由Job2处理。
  4. Job handleJobException处理异常的逻辑是,首先判断协程的context是否设置过CoroutineExceptionHandler,如果有则交由CoroutineExceptionHandler处理异常。如果没设置过则会交由AndroidExceptionPreHandler处理。


有几个需要注意的点


  1. CancellationException传播给父Job,一定会被打回。换而言之,它不会影响父Job。


  1. SupervisorJob和supervisorScope一样会把异常打回给子Job处理,它们都可以把异常掐死在内部,不往外传播。


  1. coroutineScope和withContext可以把协程的异常rethrow,也就是可以在他们外层加try catch捕获。


4. 最后



最后点下题吧。事件分发的Down事件是树的先序遍历算法(根右左)。而非Down事件(如Move、Up等事件)是根据TouchTarget生成的事件链表做的链表遍历。协程也是类似,cancel事件,先取消自己和子协程,是根据先序遍历(根左右),然后再取消父协程(父协程同样是先序遍历取消)。异常处理则是根据子节点->父节点的链表遍历请求处理异常。说是一母同胞实在是不过分。如果你没看懂,说明你的数据结构和算法知识还是比较欠缺,建议补补课。

相关文章
|
6月前
|
Dart
带你读《深入浅出Dart》十六、事件循环和协程机制(2)
带你读《深入浅出Dart》十六、事件循环和协程机制(2)
|
1月前
|
并行计算 调度 开发者
深入浅出Python协程:提升你的异步编程效率
在当今快速发展的软件开发领域,异步编程已成为提高程序性能和用户体验的关键技术。Python,作为一门广泛使用的高级编程语言,其协程(Coroutine)功能为开发者提供了强大的异步编程工具。本文将从协程的基本概念入手,通过实例深入浅出地讲解如何在Python中有效利用协程来提升异步编程的效率和可读性。我们将探讨协程的工作原理、与传统多线程/多进程相比的优势,以及如何在实际项目中应用协程来解决复杂的并发问题。通过本文的学习,读者将能够掌握Python协程的核心知识,为构建高效、可维护的异步应用奠定坚实基础。
|
9天前
|
调度 Python
Python多线程、多进程与协程面试题解析
【4月更文挑战第14天】Python并发编程涉及多线程、多进程和协程。面试中,对这些概念的理解和应用是评估候选人的重要标准。本文介绍了它们的基础知识、常见问题和应对策略。多线程在同一进程中并发执行,多进程通过进程间通信实现并发,协程则使用`asyncio`进行轻量级线程控制。面试常遇到的问题包括并发并行混淆、GIL影响多线程性能、进程间通信不当和协程异步IO理解不清。要掌握并发模型,需明确其适用场景,理解GIL、进程间通信和协程调度机制。
28 0
|
30天前
|
API 数据处理 调度
Python中的异步编程与协程应用
传统的Python编程在处理IO密集型任务时常常面临效率低下的问题,而异步编程和协程技术的引入为解决这一问题提供了有效的途径。本文将介绍Python中异步编程的基本概念,深入探讨asyncio库的使用以及协程在实际项目中的应用,旨在帮助开发者更好地理解和运用异步编程技术。
|
1月前
|
调度 Python
python协程—asyncio模块
python协程—asyncio模块
24 0
|
1月前
|
API 开发者 Python
深入浅出Python协程:提升并发编程效率
在当今高速发展的互联网时代,高并发成为了软件开发中的一个重要需求。本文将引领读者深入理解Python中的协程(Coroutine)概念,探讨其在并发编程中的应用及优势。我们将从协程的基础概念出发,通过实例讲解如何使用asyncio库来编写高效的异步代码。文章旨在帮助读者掌握协程的工作原理和使用方法,从而在实际开发中能够更好地利用Python进行高效的并发编程。
|
1月前
|
数据采集 调度 开发者
深入浅出Python协程:提升并发编程效率
本文旨在为读者揭开Python协程的神秘面纱,通过深入浅出的方式阐述其工作原理及应用场景。不同于传统的技术文章摘要,我们将以一种独特的视角,将协程比作一场精心编排的交响乐,其中每一个乐章都是一个独立的任务,共同演绎出并发编程的华丽篇章。文章将从协程的基本概念切入,通过对比线程和进程,逐步深入到事件循环、异步IO等核心机制,最后通过案例分析,让读者能够掌握使用Python协程处理高并发任务的技巧,从而提升编程效率。
|
1月前
|
程序员 开发者 Python
深入浅出Python协程:提升代码效率的秘诀
【2月更文挑战第12天】 在当今追求高效编程的时代,Python协程成为了开发者提升代码执行效率的重要工具。本文将以通俗易懂的方式,深入探讨Python协程的原理、使用方法及其在实际开发中的应用场景。通过对比传统同步编程和异步编程的差异,我们将揭示协程如何在不牺牲代码可读性的前提下,显著提高程序的运行效率。文章旨在为Python开发者提供一份全面、实用的协程学习指南,帮助他们在实际项目中更好地利用这一强大的特性。
22 2
|
2月前
|
调度 Python
什么是Python中的协程(Coroutine)?如何使用`async`和`await`进行协程编程?
什么是Python中的协程(Coroutine)?如何使用`async`和`await`进行协程编程?
27 0
|
2月前
|
程序员 Python
Python并发编程之协程与多线程对比分析
本文通过对Python中协程和多线程的特点、优缺点以及适用场景进行深入比较分析,帮助读者更好地理解并发编程中不同技术方案的选择与应用。