异常处理
-
异常类有 Error 和 Exception
- Error 类描述仓颉语言运行时,系统内部错误和资源耗尽错误,应用程序不应该抛出这种类型错误
- Exception 类描述的是程序运行时的逻辑错误或者 IO 错误导致的异常
- 简单说来,程序员能处理的叫Exception,程序员不能处理的叫Error
- 用户只能继承内置的 Exception 或其子类,不能继承Error或其子类
-
异常处理由 try 表达式完成,可分为:
- 不涉及资源自动管理的普通 try 表达式;
- 会进行资源自动管理 try-with-resources 表达式
-
try 表达式可以出现在任何允许使用表达式的地方
-
try 表达式的类型为 finally 分支除外的所有分支的类型的最小公共父类型
-
try-with-resources 表达式中一般没有必要再包含 catch 块和 finally 块,也不建议用户再手动释放资源
- 因为 try 块执行的过程中无论是否发生异常,所有申请的资源都会被自动释放
- 并且执行过程中产生的异常均会被向外抛出
-
还可以使用option来处理有值和无值的情况
// 简单说来,程序员能处理的叫Exception,程序员不能处理的叫Error
// 不能定义Error的子类
// class FatherError <: Error {}
class AnotherException <: Exception {}
// 定义父类异常
open class FatherException <: Exception {
public open func printException() {
print("I am a FatherException")
}
}
// 定义子类异常
class ChildException <: FatherException {
public override func printException() {
print("I am a ChildException")
}
}
// 普通 try 表达式
public func normalTry() {
// try 表达式可以出现在任何允许使用表达式的地方
// try 表达式的类型为 finally 分支除外的所有分支的类型的最小公共父类型
let result = try {
let a = 10
throw FatherException()
} catch (e: FatherException) {
// catchPattern 中引入的变量作用域级别与 catch 后面的块中变量作用域级别相同
// 在 catch 块中再次引入相同名字会触发重定义错误
// catch里面已经定义了e,块里面就不能再定义e这个变量了
// let e: Int = 10
println('this is a FatherException')
20
} catch (e: Exception) {
println('this is a unknown Exception')
30
} finally {
println("这里 成功或失败 都会执行")
}
}
// 实现了Resource接口,就可以认为是一种资源
// 尽量保证其中的 isClosed 函数不要再抛异常
class MyResource <: Resource {
public func isClosed(): Bool {
true
}
public func close(): Unit {
print("R is closed")
}
}
// Try-with-resources 表达式主要是为了自动释放非内存资源
public func resourceTry() {
// catch 块和 finally 块均是可选
// try 关键字其后的块之间可以插入一个或者多个 ResourceSpecification 用来申请一系列的资源
// try 关键字和 {} 之间引入的名字,其作用域与 {} 中引入的变量作用域级别相同,在 {} 中再次引入相同名字会触发重定义错误
try (r = MyResource()) {
// r变量已经引用了资源,下面不能再重复声明
// let r = 10
} catch (e: ChildException) {
} catch (e: FatherException | AnotherException) {
// 捕获异常的类型会被转换成由 | 连接的所有类型的最小公共父类
// 并将异常实例与 Identifier 定义的变量进行绑定
} catch (e: Exception) {
println("Exception happened when executing the try-with-resources expression")
} catch (_) {
// _ 可以捕获同级 try 块内抛出的任意类型的异常
// 等价于类型模式中的 e: Exception,即捕获 Exception 子类所定义的异常
} finally {
println("End of the try-with-resources expression")
}
}
class MyOptionClass {
var name: ?String = None
var arr: ?Array<?(Int) -> ?((Int) -> Unit) -> Unit> = None
}
func optionLambda(x: Bool, action: ?(() -> Unit)) {
if (x) {
action?()
}
}
public func useOption() {
// Option 类型是一种 enum 类型,可以使用 enum 的模式匹配来实现对 Option 值的解构
func getOrThrow(a: ?Int64) {
// 使用模式匹配
match (a) {
case Some(v) => v
case None => throw NoneValueException()
}
}
let a = Some(1)
let b: ?Int = None
// 表达式 e1 ?? e2,当 e1 的值等于 Some(v) 时返回 v 的值,否则返回 e2 的值
let c = b ?? 3
//? 需要和 . 或 () 或 [] 或 {}(特指尾随 lambda 调用的场景)一起使用,用以实现 Option 类型对 .,(),[] 和 {} 的支持
let myCls = Some(MyOptionClass())
// 搭配.
myCls?.name
let optionFunc = Some({x: Int => println(x)})
// 搭配()
optionFunc?(2)
let optionArray = Some([1, 2, 3])
// 搭配[]
optionArray?[5]
// 搭配 {}
let f1: ?((Int64) -> Unit) -> Unit = None
f1? {i => println(i)}
let f2: ?(Int, (Int) -> Unit) -> Unit = None
f2?(5) {x => println(5 * x)}
// 来个三个搭配一起的
myCls?.arr?[0]?(5)? {x => println(5 * x)}
// 使用getOrThrow函数
// 对于 ?T 类型的表达式 e,可以通过调用 getOrThrow 函数实现解构
// 当 e 的值等于 Some(v) 时,getOrThrow() 返回 v 的值,否则抛出异常
let e: ?Int = 2
e.getOrDefault() {5}
_ = e.getOrThrow()
_ = e.getOrThrow() {
UnsupportedException('不支持的异常')
}
}
并发编程
- 线程分为语言级别实现的线程和系统级别的native线程
仓颉编程语言的线程模型是抢占式,如果有多线程操作一个共享变量,需要有同步机制- 语言线程是编程语言中 并发模型的基本执行单位,目的是屏蔽底层实现细节
仓颉语言的线程实现采用 M:N 线程模型,即 M 个语言线程在 N 个 native 线程上调度执行,其中 M 和 N 不一定相等- 仓颉线程处于用户态,它的执行依托于系统线程。也许仓颉线程理解为语言级别的任务更合适
- 使用关键字 spawn 并传递一个无形参的 lambda 表达式来创建一个仓颉线程并将闭包运行于该线程
- 仓颉线程会返回一个Future<T>类型的结果,其中T是线程内任务的返回值类型。可以通过future上的方法等待线程或者获取值
- 每个 Future<T> 对象都有一个对应的仓颉线程。这个对象可以通过future的thread属性或者Thread对象的currentThread静态成员属性获取到
通过 Future\<T\> 的 cancel() 方法向对应的线程发送终止请求,该方法不会停止线程执行。开发者需要使用 Thread 的 hasPendingCancellation 属性来检查线程是否存在终止请求- 仓颉编程语言提供
原子操作,互斥锁以及条件变量来确保数据的线程安全 每一个线程都有独立的一个存储空间来保存线程局部变量。在每个线程可以安全地访问他们各自的线程局部变量,而不受其他线程的影响
package cj_exercise.study
import std.sync.{sleep, AtomicInt64, AtomicBool, AtomicReference, ReentrantMutex, MultiConditionMonitor}
import std.time.{Duration, DurationExtension}
import std.collection.ArrayList
private func spawnDemo() {
let ret = spawn {
=>
println("启动一个仓颉新线程,执行100ms的睡眠")
sleep(100 * Duration.millisecond)
println("新线程 100ms 后继续执行")
if (Thread.currentThread.hasPendingCancellation) {
// 外部将此线程标记为cancel,后续代码需要业务层自己控制不要执行
println('检测到子线程被取消,后续代码不再执行,返回-1')
return -1
}
return 5
}
println("future的thread的id属性 ${ret.thread.id}")
println('阻塞当前线程10_000 纳秒 并等待结果')
let value = ret.get(10_000)
println('10_000 纳秒后 将子线程标记为取消执行')
println('此时获得的子线程的返回值value是 ${value}')
ret.cancel()
let value2 = ret.get()
println('子线程的返回值value2是 ${value2}')
}
class ReferenceClass {}
// 全局变量 属于共享临界资源 可在线程中修改
var global_count = 0
private func cocurrentSyncDemo() {
// 原子操作
let atomicInt = AtomicInt64(0)
let atomicBool = AtomicBool(false)
let atomicReference = AtomicReference(ReferenceClass())
// 互斥锁
let mutex = ReentrantMutex()
// 条件变量
// let conditionMutext = MultiConditionMonitor()
// 线程局部变量
let tl = ThreadLocal<Int>()
let fuList = ArrayList<Future<Int64>>()
for (i in 0..100) {
let fut = spawn {
// sleep 函数会阻塞当前运行的线程,该线程会主动睡眠一段时间,之后再恢复执行,其参数类型为 Duration 类型
sleep(Duration.microsecond)
// 设置局部变量
tl.set(i)
// 进行原子操作
atomicBool.compareAndSwap(false, true)
atomicInt.fetchAdd(1)
// mutex.lock()
// // global_count 是共享资源,访问前后需要 加锁、解锁
// global_count += 1
// mutex.unlock()
// 也可使用synchronized来成对处理lock和unlock
synchronized(mutex) {
global_count += 1
}
println('线程局部变量 ${tl.get().getOrThrow()}')
return i
}
fuList.append(fut)
}
// 等待所有线程结束
for (f in fuList) {
f.get()
}
}
public func cocurrentDemo() {
cocurrentSyncDemo()
}
参考资料
- 仓颉编程语言开发指南 developer.huawei.com/consumer/cn…
- 仓颉编程语言白皮书 developer.huawei.com/consumer/cn…
- 仓颉编程语言语言规约developer.huawei.com/consumer/cn…