async与await:

我们已经知道,actor中的public (shared)函数的返回值类型都必须为"a future",即以async修饰的其它类型,如async Nat。要访问async 值的结果,future的接受者要使用await表达式。可以总结为:

await 作为广义上的左值,要接受一个async _ 作为右值

遇到trap时程序回滚到何处?

A trap是一种不可恢复的运行时故障,例如,由零除、数组索引越界、数值溢出、循环耗尽或断言故障引起。

一种原子共享函数,其执行陷阱对封闭参与者或其环境的状态没有明显影响—任何状态更改都将被还原,它发送的任何消息都将被撤销。事实上,所有状态更改和消息发送在执行期间都是暂时的:它们只有在到达成功的提交点之后才提交。

在到达以下三个“提交点”之后,所有之前的状态更改和消息发送无法被撤回(回滚):

  • 通过最终产生一个结果来隐式退出一个public (shared)函数时;
  • 通过return或throw expressions来显式退出时;
  • 遇到显式的await表达式时。

接下来举个例子来感受一下:

actor{
    var s = 0; 
    var pinged = false; 
    public func show() : async (Nat, Bool) {
        (s, pinged)
    };

    public func ping() : async () { 
        pinged := true; 
    }; 

    // an atomic method 
    public func atomic() : async () { 
        s := 1; //被回滚
        ignore ping(); //被回滚,因为没有await,还没有真正调用ping()
        ignore 0/0; // trap! 
    };

    // a non-atomic method 
    public func nonAtomic() : async () { 
        s := 1; 
        let f = ping(); 
        s := 2; 
        await f; 
        s := 3; // this will not be rolled back! 
        await f; //会重新调用ping(),f的实质不是ping()的返回值,而是类似于函数的调用接口
        ignore 0/0;// trap! 
    }; 
};

在test_1.mo的程序中,当我们调用atomic()函数后,我们会发现,s的值并没有改变为1,pinged的值也未被改为true。这是因为在遇到ignore 0/0;这个trap时,会直接往回回滚,直到遇见“提交点”。而atomic()函数中并无这样的“提交点”,所以atomic()函数会被完全回滚。

而当我们调用nonAtomic()函数后,我们会发现,s变为3,pinged变为true。这是因为程序只能回滚到最后一个await f;“提交点”。之前的状态更改均已提交,无法撤回。

再看一下代码,我们把atomic函数中的ignore ping();改为await ping();之后再来测试一下。

actor{
    var s = 0; 
    var pinged = false; 
    public func show() : async (Nat, Bool) {
        (s, pinged)
    };

    public func ping() : async () { 
        pinged := true; 
    }; 

    // an atomic method 
    public func atomic() : async () { 
        s := 1; //不会被回滚
        await ping(); //不会被回滚
        ignore 0/0; // trap! 
    };

    // a non-atomic method 
    public func nonAtomic() : async () { 
        s := 1; 
        let f = ping(); 
        s := 2; 
        await f; 
        s := 3; // this will not be rolled back! 
        await f; //会重新调用ping(),f的实质不是ping()的返回值,而是类似于函数的调用接口
        ignore 0/0;// trap! 
    }; 
};
2021-11-04-下午7.09.28-1024x725.png
- 测试结果 -

我们发现,await ping();之前的代码并没有被回滚,s和pinged的状态更改均已提交。这符合前面所说的规则。

深入理解:

我举这两个例子来对比是想再表达另外一点:

这里的ping()函数的返回值future可以认为还是这个函数,并不是函数调用的结果,如nonAtomic函数中的let f = ping();,这里的f并不是ping函数中的运算结果,而是a future,还是代表这个函数,函数体并未运行,f的type为async (),而不是(),需要ignore来“忽略这个返回值”(如test_1.mo的atomic函数中所示那样),函数返回值并未真正参与到运算结果中。而这里的future(如f),可以放在await后面来组成一个await表达式,这个表达式的结果就是ping函数真正的运算结果,type为(),不需要ignore(如test_2.mo的atomic函数中所示那样)。

Motoko NFT
- Motoko -

A Student on the way to full stack of Web3.