Parallet - My Dynamic Language - 一款异步编程语言

  Parallet - My Dynamic Language - 一款异步编程语言

简介:

      Parallet是笔者自创的一种新的编程语言. 当初的定位是DotNet下的异步脚本, 用来弥补C#对异步编程的不足.  (笔者想实现一些异步操作超多超复杂的服务器应用, 但是用C#做起来超难. )

    这个项目已经开启了接近一个月. 目前的进度, 可以参考 http://www.parallet.net 上的描述 , 也可以在本博客里, 找到第一篇简介, 和最近的一些心得. 

    就在昨天, 笔者完成了初步的动态编译到IL的实现. 让大部分不需要异步执行的函数, 编译成CLI的方法. 这些函数由解释执行转换为编译执行后, 性能提高了100多倍. (解释执行的性能和VBScript差不多)

    基本上, 现在很多架构上的设计, 都已经完成. 大方向比较明确, 剩下的, 都是无穷无尽的细节问题.


异步:

    在异步编程方面, 由于新语言的异步函数的特点, 让它除了支持传统的命令式编程, 还支持新的异步方式编程, 让开发者可以用传统的命令式语法去编写异步操作, 一次过摆脱为了异步而异步的各种麻烦的编程手法.

    而且, 这个新的语法体系里, 函数默认是异步的, 系统会自动分析哪些函数需要异步处理, 而哪些不需要. 所以, 这个语言并没有为异步操作而创建任何一个关键字. 非常简单容易接受.

    实际上一时之间要描述出这个新的异步语言, 是非常非常难. 这表现在多个方面:

        - 笔者的语文水平极差, 表达能力极差, 心里所想的东西并不能很好地表达出来.

        - 和传统编程一摸一样的语法, 这是优点, 但也导致读者会认为这根本就和异步编程没什么关系..

        - 如果读者没有做过异步编程的工作, 那么就无法知道异步编程有多麻烦,
          不知道异步编程的麻烦之处, 也不会明白异步函数调用异步函数的好处.

    如果说代码就是文档的话, 那么或许用代码来解释这个异步函数的概念, 可能是最好的. 以下就用例子来慢慢说明异步函数的概念与意义.


示例:

    以下示例, 力求简单, 都是随便手写, 用JS语法, 目的是让大家更容易理解.

    传统,阻塞型,同步调用
//JS脚本(非Parallet代码)
function
LoadConfig()
{
var xhr=new XMLHttpRequest();
xhr.open(
"GET","config.aspx",false);
xhr.send(
"");
return ParseConfig(xhr.responseText);
}
    上面代码, 通过给xhr.open传递一个false, 指定为sync模式. 当xhr.send("")执行时, 除非服务器返回或者出错, 否则当前代码就会被阻塞, 当前的线程也会被阻塞, 浏览器也不会响应.

    在UI线程里, 发生阻塞, 界面假死, 对用户是非常不友好的事. 为了解决这个问题, xhr提供了异步模式

    传统,回发型,异步调用:
复制代码
//JS脚本(非Parallet代码)
function
LoadConfig()
{
var xhr=new XMLHttpRequest();
xhr.open(
"GET","config.aspx",true);
xhr.onreadystatechange
=function()
{
if(xhr.readyState<4)return;
ParseConfig(xhr.responseText);
}
xhr.send(
"");
}
复制代码
    通过给xhr.open传递一个true, 指定为async模式. xhr.send("")后, 就立刻返回, 不会发生阻塞, 但程序也不会立刻得到结果. xhr得到服务器的响应后, 执行onreadystatechange指定的方法, 来通知它本身的调用者去处理异步的结果.

    咋一看, 两个LoadConfig用了不同的方式, 实现了相同的事了不 ?  没有 !

      假如有人写了这么一段代码 :
//JS脚本(非Parallet代码)
var
config=LoadConfig();
InitMySystem(config.OptionName);
    阻塞方式的LoadConfig能阻塞线程, 等待服务器回复, 并且把结果返回给它的调用者. 

    但是, 如果把LoadConfig里的xhr改成异步调用, 那么config=LoadConfig()就不能正确地给config传递服务器结果了.

    所以, 要解决这个问题, 方案是建立新的函数 LoadConfigAsync
复制代码
//JS脚本(非Parallet代码)
function
LoadConfigAsync(callback)
{
var xhr=new XMLHttpRequest();
xhr.open(
"GET","config.aspx",true);
xhr.onreadystatechange
=function()
{
if(xhr.readyState<4)return;
callback(ParseConfig(xhr.responseText));
}
xhr.send(
"");
}
复制代码
    而在外面的代码, 也需要响应改变成
//JS脚本(非Parallet代码)
LoadConfigAsync(
function(config)
{
InitMySystem(config.OptionName);
});
    这种方式, 就是目前大多数编程语言的现状. 无论是JS,还是win32下的异步socket,还是DotNet的BeginXxx/EndXxx, 都是通过传递回发函数(事件也是传递回发函数的形式).

    这种方式, 一旦函数之间的嵌套比较深入, 就会变得非常复杂, 而且它很难实现各种循环, 更难实现的, 就是如何处理异步错误. 很多时候, 代码给拆分出非常多块, 基本上每一小块都要try/catch, 处理异常后要返回也是非常麻烦.

    所以, 传统的异步编程, 非常不直观, 非常麻烦. 这就是Parallet希望能解决的问题.

    在Parallet里, 所有的函数, 默认都是异步函数. 开发者不需要考虑阻塞不阻塞的问题, 这是语言需要帮开发者解决的问题.

    上面的问题, 在Parallet是这样写的 :  (语法形式,XHR为虚构)
复制代码
//PARALLET代码 function LoadConfig()
{
var xhr=new XHR();
xhr.open(
"get","server.aspx");
xhr.send(
"");
return ParseConfig(xhr.responseText);
}
var config=LoadConfig();
InitMySystem(config.OptionName);
复制代码
    在Parallet里, xhr.open不再需要指定同步和异步. 因为默认就是异步.  但是, xhr.send("")之后, 服务器没有返回, 调用链会被挂起, 不会阻塞当前线程. 当前线程会继续跑去执行其他事情.  当server.aspx返回后, 当前线程又会跑回来, 继续执行ParseConfig的部分, 然后把LoadConfig的结果返回给config变量, 继续执行外面的代码.

    可以看出, Parallet的主要思想, 就是释放当前线程, 去干别的事, 然后再回来继续执行代码.

    这个看上去好像很简单, 但是目前的主流语言都无法直接在CPU指令或IL的层次上实现它. 主要原因就是目前的计算机体系, 是用stack来储存函数调用链上的各种参数返回值和临时变量的. 如果要实现函数执行到一半就释放线程, 那么操作系统就必须要把stack上的数据全部储存起来, 当需要恢复函数的执行时, 又需要把数据恢复到stack上, 然后让线程继续.

    而Parallet也一样, 并没有在IL上实现它, 所以Parallet执行异步函数时, 只能解释执行, 所有的参数返回值临时变量, 都是储存在虚拟机的特殊stack上, 而不是线程的stack. 也就是说, 不存在这个保存和恢复数据的过程, 但性能的损失也是非常多. 基于这个解释型的虚拟机, 任何函数都能随时挂起, 线程可以随时切换去做其他的事.

    也许大家还没有看到实质的好处, 因为例子实在太简单. 如果在外面套一层try/catch, 就能看得出异步函数的优越性了
复制代码
//PARALLET代码
function
LoadConfig()
{
var xhr=new XHR();
xhr.open(
"get","server.aspx");
xhr.send(
"");
return ParseConfig(xhr.responseText);
}
try
{
var config=LoadConfig();
InitMySystem(config.OptionName);
}
catch(x)
{
alert(
"initialize failed.");
}
复制代码
    上述的代码,无论在异步操作的任何环节出错, 错误都能被统一catch住.  如果是换成传统的LoadConfigAsync, 要让多个'被分割'的函数里统一处理错误? 非常难..非常难...(无限回音...) ,  这也是笔者用C#来编写那个服务器应用时最恼火的事.

最后

    上面用代码来说明了一下Parallet和传统异步编程的区别. 可以看出,Parallet能把异步问题大大地简化掉. 

    没有新语法,没有新关键字,一切都是那么的简单, 自然..

    在这个模式下, 有很容易的方式去处理各种异步,并发, 多线程的综合问题.  这个笔者会陆续补充上.


posted @   Parallet  阅读(1787)  评论(15编辑  收藏  举报
编辑推荐:
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· 字符编码:从基础到乱码解决
· Open-Sora 2.0 重磅开源!
点击右上角即可分享
微信分享提示