仓颉语法-异常处理与异步编程

413 阅读6分钟

异常处理

  1. 异常类有 Error 和 Exception

    • Error 类描述仓颉语言运行时,系统内部错误和资源耗尽错误,应用程序不应该抛出这种类型错误
    • Exception 类描述的是程序运行时的逻辑错误或者 IO 错误导致的异常
    • 简单说来,程序员能处理的叫Exception,程序员不能处理的叫Error
    • 用户只能继承内置的 Exception 或其子类,不能继承Error或其子类
  2. 异常处理由 try 表达式完成,可分为:

    • 不涉及资源自动管理的普通 try 表达式;
    • 会进行资源自动管理 try-with-resources 表达式
  3. try 表达式可以出现在任何允许使用表达式的地方

  4. try 表达式的类型为 finally 分支除外的所有分支的类型的最小公共父类型

  5. try-with-resources 表达式中一般没有必要再包含 catch 块和 finally 块,也不建议用户再手动释放资源

    • 因为 try 块执行的过程中无论是否发生异常,所有申请的资源都会被自动释放
    • 并且执行过程中产生的异常均会被向外抛出
  6. 还可以使用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('不支持的异常')
    }
}

并发编程

  1. 线程分为语言级别实现的线程和系统级别的native线程
  2. 仓颉编程语言的线程模型是抢占式,如果有多线程操作一个共享变量,需要有同步机制
  3. 语言线程是编程语言中 并发模型的基本执行单位,目的是屏蔽底层实现细节
  4. 仓颉语言的线程实现采用 M:N 线程模型,即 M 个语言线程在 N 个 native 线程上调度执行,其中 M 和 N 不一定相等
  5. 仓颉线程处于用户态,它的执行依托于系统线程。也许仓颉线程理解为语言级别的任务更合适
  6. 使用关键字 spawn 并传递一个无形参的 lambda 表达式来创建一个仓颉线程并将闭包运行于该线程
  7. 仓颉线程会返回一个Future<T>类型的结果,其中T是线程内任务的返回值类型。可以通过future上的方法等待线程或者获取值
  8. 每个 Future<T> 对象都有一个对应的仓颉线程。这个对象可以通过future的thread属性或者Thread对象的currentThread静态成员属性获取到
  9. 通过 Future\<T\> 的 cancel() 方法向对应的线程发送终止请求,该方法不会停止线程执行开发者需要使用 Thread 的 hasPendingCancellation 属性来检查线程是否存在终止请求
  10. 仓颉编程语言提供 原子操作,互斥锁以及条件变量 来确保数据的线程安全
  11. 每一个线程都有独立的一个存储空间来保存线程局部变量。在每个线程可以安全地访问他们各自的线程局部变量,而不受其他线程的影响
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()
}

参考资料

  1. 仓颉编程语言开发指南 developer.huawei.com/consumer/cn…
  2. 仓颉编程语言白皮书 developer.huawei.com/consumer/cn…
  3. 仓颉编程语言语言规约developer.huawei.com/consumer/cn…