jarlyyn 发表于 2023-6-6 13:42:18

newstart 发表于 2023-6-6 01:32 PM
还好吧
做好每一个单独的Command模块就可以了
反正目前运行下来没啥问题 ...

异步肯定要判断是否成功啊……

成功成功处理,失败失败的处理……

newstart 发表于 2023-6-6 13:50:03

jarlyyn 发表于 2023-6-6 01:42 PM
异步肯定要判断是否成功啊……

成功成功处理,失败失败的处理……

那是当然,异步情况下必须要处理
所以我用自己开发的客户端,实现了一个Command类,专门处理这种情况
Command相当于Alias+Trigger+Timer的组合

异步执行后,有4种不同状态结果,分别为成功、失败、重试、超时,所有的处理都要在Command类型里面完成的
app在https://github.com/crapex/pymud
异步Command可以看object.py中的SimpleCommand实现


所以,针对移动这个命令,我的实现对上述所有情况都进行了处理
self.cmd_move      = Configuration.CmdMove(self.session, self.mapper, id = "cmd_move", succ_tri = self.tri_loc, fail_tri = self.triggersInGroup("movefail"), retry_tri = self.triggersInGroup("moveretry"))


jarlyyn 发表于 2023-6-6 14:23:47

newstart 发表于 2023-6-6 01:50 PM
那是当然,异步情况下必须要处理
所以我用自己开发的客户端,实现了一个Command类,专门处理这种情况
Com ...

这个应该是两个概念吧。

异步是异步,封装是封装。

异步一般目前比较常见的是类似js里Promoise的概念

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

myPromise
.then((value) => `${value} and bar`)
.then((value) => `${value} and bar again`)
.then((value) => `${value} and again`)
.then((value) => `${value} and again`)
.then((value) => {
    console.log(value);
})
.catch((err) => {
    console.error(err);
});


封装的话,我这里主要是把tri,alias,timer处理成envent,然后就是标准的有限状态擅长的领域了。

提这个主要是觉得1和2的作用域未必是完全匹配的。

newstart 发表于 2023-6-6 15:20:17

本帖最后由 newstart 于 2023-6-6 03:28 PM 编辑

jarlyyn 发表于 2023-6-6 02:23 PM
这个应该是两个概念吧。

异步是异步,封装是封装。

可能是我表述的问题。
python的asyncio就是异步IO的标准库,和你说的promise是类似的,Python里面是使用asyncio.Future类来实现类似功能的
我前面说的是,Command相当于Alias+Trigger+Timer的组合,这里是相当于,确实可以理解成几个类型的封装,
但最大的核心区别,我的Trigger实现是既支持同步(回调函数模式),也支持异步(协程/事件循环模式)。
我在实现的Command类里面,所有对触发器的处理,都是使用异步事件模式实现的,其核心Command.execute方法也是使用async关键词修饰的,也是属于异步命令



同步情况下:
<font size="2"><font color="#000000">if tri.matched(msg):</font></font>
<font size="2"><font color="#000000">   do_something</font></font>
异步情况下:
<font size="2"><font color="#000000">class Trigger:</font></font>
<font size="2"><font color="#000000">   # trigger的异步等待方式</font></font>
<font size="2"><font color="#000000">   async def triggered(self):</font></font>
<font size="2"><font color="#000000">       self.event.clear()</font></font>
<font size="2"><font color="#000000">       await self.event.wait()</font></font>
<font size="2"><font color="#000000">       return trigger_capture_info</font></font>
在异步调用Trigger时,是使用下面的函数方式:
<font size="2"><font color="#000000">async def execute():</font></font>
<font size="2"><font color="#000000">    write_some_command</font></font>
<font size="2"><font color="#000000">    await tri.triggered()</font></font>
<font size="2"><font color="#000000">    do_something</font></font>执行上述execute函数只能在async定义的异步函数体中执行,或者使用asyncio.create_task创建任务,放到事件循环中执行。

python的asyncio库介绍:
https://docs.python.org/zh-cn/3.10/library/asyncio.html
Python异步协程(asyncio详解)
https://www.cnblogs.com/Red-Sun/p/16934843.html

jarlyyn 发表于 2023-6-6 16:05:07

newstart 发表于 2023-6-6 03:20 PM
可能是我表述的问题。
python的asyncio就是异步IO的标准库,和你说的promise是类似的,Python里面是使用as ...

promise是promise,async是async。

js的异步发展史就是 callback->promise->async这条线的三个阶段。

promise的主要特点是可以串起来,可以有 解决/异常两条分路,以及可以储存和管理。

建立了promise并不一定执行

然后是async的问题

包括你引用的文档也写的很清楚

aysnc(和线程)解决的是并发问题。

并发问题是什么意思呢?

以房间名的触发为例。


[*]看见房间名,触发一个async,起了一个协程。
[*]又看到一次房间名,又触发一个sync,又起了一个携程。
[*]这时候你拥有了两个优先级相同的,并发的协程,都在等待npc信息。
[*]看到npc了。两个协程都触发了,会发两次kill,然后两个协程都结束。


我不知道你的客户端架构是怎么样的。但一般mush的lua机器人我是不建议用异步库的,容易出问题。

一般来说,我更建议和浏览器环境下的js一样,实际上的单线程,并发/协程由客户端主程序去维护,并防止数据竞争。



newstart 发表于 2023-6-6 19:24:32

本帖最后由 newstart 于 2023-6-6 07:30 PM 编辑

jarlyyn 发表于 2023-6-6 04:05 PM
promise是promise,async是async。

js的异步发展史就是 callback->promise->async这条线的三个阶段。

emmmm....以下继续进行技术探讨:)
我的理解和你的理解可能有些差异,并发是与并行来对应的;异步是与同步来对应的,这两者之间没有矛盾吧...
MushClient是不能很好的支持python异步的,如果lua可以用协程实现,但使用python时,只能使用基于生成器的协程(我以前就是这么干的),如果想使用async/await,由于两者之间不在同一个事件循环中(也就是不同的线程中),所以根本不能支持async/await语法运行
也是基于这个原因,我决定开发自己的客户端,以完整利用async/await的特性。

再解释下,我的客户端架构就是单线程的,异步单线程,事件循环由主对象PyMud进行维护;
客户端实现的Trigger的异步处理函数并非在Trigger被触发时调用,Trigger触发时仅会调用同步函数,在同步函数中,会设置其可等待的Future对象(我使用的asyncio.Event)的状态为done;
异步的async def triggered()仅在被函数代码await tri.triggered()调用时,才产生协程并予以运行...而这个等待的代码只有在Command的异步执行函数中才会被调用。
以你说的房间名举例:
tri.triggered()是一个协程,并不会在收到房间名时被调用,它仅会在Command的look指令发出后,被await tri.triggered()代码执行时才会被调用,以等待房间名,其状态直接会通过语句结果返回,举例:

    class CmdLook(Command):
         # 其他代码省略,仅留下execute执行函数
         async def execute(self, cmd = "look", *args, **kwargs):
                try:
                  self.reset()
                  # 1. 输入命令
                  self.tri_start.enabled = True
                  self.tri_exits.reset()
                  self.session.writeline(cmd)


                  # 2. 等待房间出口被触发   
                  done, pending = await asyncio.wait(, timeout = 5)
                  if len(done) > 0:
                        task = tuple(done)
                        state = task.result()
                        # 此处原有room 的有关数据设定,此处删除了
                        # 执行默认onSuccess
                        self._onSuccess(room)
                        result = self.SUCCESS
                  else:
                        # timeout,同success
                        self._onTimeout()
                        result = self.TIMEOUT
                  
                  self.reset()
                  return self.State(result, room)
               
                except Exception as e:
                  self.error(f"异步执行中遇到异常, {e}, 类型为 {type(e)}")
                  self.error(f"异常追踪为: {traceback.format_exc()}")


这个不是与promise.then().catch()一样吗?
另外,你举例房间名触发的情况,当然,如果连续两次look,的确会触发两次协程,如果输入命令的速度过快,两次协程都会在第一次房间名出来的时候执行完毕,第二次则不会响应;
对于这个的处理方式,每一个Command实现时,其内部的awaitable对象都被存储,在下一次命令被执行时,如果上一个awaitable对象(如tri.triggered()协程)还处于pending状态,则会手动取消,以确保每一个Command只有一个awaitable对象起作用。
另外,我又在网上搜索了有关并发、并行、同步、异步的区别;以及promise、async/await的区别。js的新版中也是async/await,就如你说的js的异步发展史就是 callback->promise->async这条线的三个阶段。
网上说所,js中,async/await更像是一种语法糖,async定义的函数就是一个promise对象,是promise对象的改良版

参考资料:
1.1、程序中所谓「异步」和「并发」的区别有哪些:https://worktile.com/kb/ask/38122.html
1.2、区别:并行、并发、同步、异步: https://blog.csdn.net/wangxiaosu0501/article/details/124602000
2.1、promise和async await区别(我觉得这一篇写的特别完整):https://www.jianshu.com/p/0431e209dc0f
2.2、Promise和async/await的区别:https://blog.csdn.net/weixin_47277125/article/details/123754080
2.3、promise和async await的区别:https://blog.csdn.net/weixin_44246717/article/details/118659810
2.4、promise和async await的区别:https://blog.csdn.net/sinat_36728518/article/details/107293428?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-0-107293428-blog-118659810.235^v38^pc_relevant_anti_vip_base&spm=1001.2101.3001.4242.1&utm_relevant_index=1

jarlyyn 发表于 2023-6-6 19:48:37

newstart 发表于 2023-6-6 07:24 PM
emmmm....以下继续进行技术探讨:)
我的理解和你的理解可能有些差异,并发是与并行来对应的;异步是与同步 ...

在我的理解里

promise 只是一个数据结构(callback的队列)。并不需要执行。
promise可以用async去消费他,理论也可以用自己 方式自行消费。

提到async/协程的问题就是,协程在我理解里就是一个在同一个线程里分配cpu事件的机制,挂起当前协程。

而mud机器人如果使用挂起的机制的话,复杂度很高(牵涉到怎么保证取消挂起),所以我才会觉得有问题。


newstart 发表于 2023-6-6 21:36:52

本帖最后由 newstart 于 2023-6-6 09:41 PM 编辑

jarlyyn 发表于 2023-6-6 07:48 PM
在我的理解里

promise 只是一个数据结构(callback的队列)。并不需要执行。

我查了下,在js中,promise对象也是使用await语句来进行消费,与async定义的对象一样,而且js与python的代码逻辑几乎完全一致
所以上面是一样的啊,async定义的函数也只是一个数据结构,不需要执行,需要使用await去消费;
如async函数定义

async def func():
    print('aaa')

async def main():
    #下面这句不会调用函数输出aaa,只会生成一个包含func()函数的协程对象
    obj = func()
    #下面这句才会真正执行func函数,输出aaa
    await obj

# 主入口
asyncio.run(main())
协程对象obj只会执行一次,执行完毕后其状态会自动从pending变成done,然后无法再次运行


我理解你上面说的还是传统的协程实现,在lua里,这种实现方式是
使用coroutine.create()来创建,
使用coroutine.yield()来等待
使用coroutine.resume()来恢复

下面是我以前写的lua携程代码(MushClient)

function Room:CatchStart(cmd, after)
    if cmd == nil or cmd == "" then cmd = "look" end
    if after == nil or after <= 0.1 then after = 0.1 end
    self:Update()
    Room.catch_co = coroutine.create(function()
      world.EnableTriggerGroup("gps.catchroom",false)      
      world.EnableTrigger("Room_delay",true)
      world.EnableTrigger("Room_name",true)
      world.DoAfter(after, cmd)
      world.EnableTimer("Room_overtimer", true)
      world.ResetTimer("Room_overtimer")
      local r = coroutine.yield() --挂起
      if r then
            self:CatchEnd()
      else
            self:CatchFail()
      end
    end)
coroutine.resume(Room.catch_co)
end
在python以前的版本里,使用基于生成器的协程,是
带有yield指令的函数作为协程
使用next调用协程推进(恢复)

使用异常捕获StopIteration来确认其中止状态


下面是我以前写的python协程代码(MushClient)
<div>
</div><div>    def _onSuccess(self, sender, args):
      try:
            self._generator.send((CommandState.Success, sender, args))
      except StopIteration:
            #print("the command: '%s' has executed successfully" % self._command)
            pass
               
    def _onFail(self, sender, args):
      try:
            self._generator.send((CommandState.Failed, sender, args))
      except StopIteration:
            #print("the command: '%s' hasn't executed failed" % self._command)   
            pass
   
    def _onTimeout(self, sender, args):
      try:
            self._generator.send((CommandState.Timeout, sender, args))
      except StopIteration:
            #print("the command: '%s' hasn't executed timeout" .% self._command)   
            pass
   
    def _beforeExecute(self, **params):
      '''
      default before execute:
          enable the group
      '''
      en = params.get('autoenable', True)
      self.Enable(en)
   
    def _afterExecute(self, state):
      self.Enable(False)
      self._state = state
      self._doEvent("AfterDone")
      self._state = CommandState.NotStart

    def Enable(self, value = True):
      self._enabled = value
      for tri in self._triggers.keys():
            self._triggers.Enabled = value
   
    def _coroutine(self):
      state, _, _ = yield
      self._afterExecute(state)

    def Execute(self, cmd, **params):
      self._command = cmd                                       # command text
      self._beforeExecute(**params)
      self._generator = self._coroutine()
      next(self._generator)
      self.mush.Execute(self._command)</div>
在上面这几种情况下,挂起/恢复/取消机制非常复杂,写出来的代码基本没有可读性
后来版本升级后,可以@asyncio.coroutine标记协程和yield from语法调用(3.3版为async/yield from),但还是很难编写和读懂代码逻辑

因此,Python自3.5版引入了async/await语法(js是es7中引入的),就是希望将基于协程的异步实现的可以像同步函数一样
其实现逻辑是将未来产生的对象使用Future类(一翻译为期物,指被期待的物体)进行封装,这样就可以由事件循环自动维护协程的运行/等待/中止状态

下面是我现在写的异步代码(PyMUD,自动少林跳楼)

       async def jumptower(self):
            "跳楼轻功主循环"
            result = await self._runto.execute('rt {}'.format(self.PLACE_TOWER))
            if result == self.SUCCESS:
                self._triggers["skm_levelup"].enabled = True
                self._triggers["skm_towersq"].enabled = True
                self._triggers["skm_towerup"].enabled = True
                self.session.writeline("set brief 3")
               
                dir = "enter"
                times = 0
                while True:
                  awt_tasks = []
                  awt_tasks.append(asyncio.create_task(self._triggers["skm_towersq"].triggered(), name = "tsk_towersq"))
                  awt_tasks.append(asyncio.create_task(self._triggers["skm_towerup"].triggered(), name = "tsk_towerup"))
                           
                  #await self._move.execute(dir)
                  self.session.writeline(dir)
                  done, pending = await asyncio.wait(awt_tasks, return_when = "FIRST_COMPLETED")
                  for task in list(pending):
                        task.cancel()
                  
                  tasks_done = list(done)
                  iflen(tasks_done) == 1:
                        task = tasks_done
                        state, name, line, wildcards = task.result()
                        if name == self._triggers["skm_towerup"].id:
                            await asyncio.sleep(0.1)
                            floor = wildcards
                            if floor == "七":
                              self.session.writeline("exert regenerate")
                              self.session.writeline("exert recover")
                              dir = "out"

                              times += 1
                              if times >= 100:
                                    times = 0
                                    await self._lifemisc.execute("feed")
                            else:
                              dir = "up"

                        elif name == self._triggers["skm_towersq"].id:
                            # 判断技能等级是否已达上限
                            if self._levelup:
                              self._levelup = False
                              await self._skills.execute("skills")      # 等待以使skills命令获取完相关技能等级
                              dodge = self.session.getVariable("dodge", None)
                              if dodge:
                                    dodge_lvl = dodge
                                    dodge_max = dodge
                                    if dodge_lvl >= dodge_max:
                                        self.info(f"基本轻功已达等级上限 {dodge_max}", '技能')
                                        break
                                       
                            await asyncio.sleep(2)
                            dir = "enter"

                  if self._halted:
                        self.info("跳楼被手动中止", '技能')
                        break

                self._triggers["skm_levelup"].enabled = False
                self._triggers["skm_towersq"].enabled = False
                self._triggers["skm_towerup"].enabled = False
                return self.SUCCESS
            
            else:
                self.error(f"未抵达跳楼起始点 {self.PLACE_TOWER}, 请检查重试", '技能')
                return self.FAILURE

不同的协程实现方式,都可以实现同样的功能,但是代码编写难度和可读性完全是天差地别

在下面的js异步发展史中提到,promise本质上并没有解决js的回调地狱问题,里面这句话说的很好:
async/await 使得异步代码看起来像同步代码,异步编程发展的目标就是让异步编程看起来像同步一样。
异步编程的最高境界就是不用关心他是不是异步,因此async/await被很多人为是异步编程的终极解决方案。


异步编程 101:Python async await发展简史:https://blog.csdn.net/weixin_34397291/article/details/91401107
js异步发展史:https://blog.csdn.net/qq_36850967/article/details/93745949

jarlyyn 发表于 2023-6-6 21:44:28

newstart 发表于 2023-6-6 09:36 PM
我查了下,在js中,promise对象也是使用await语句来进行消费,与async定义的对象一样,而且js与python的代 ...

那我的疑惑是,这里真的需要协程么?
举个例子,我的跳塔代码

(function (App) {
    const Round = 10
    App.Quest.Tiaota = {}
    App.Quest.Tiaota.Data = {}
    App.Quest.Tiaota.Start = function (param) {
      App.Quest.Tiaota.Data = {
            Count: 0,
      }
      if (param) {
            param = param.trim()
      }
      let max = 0
      if (param) {
            let pmax = param - 0
            if (isNaN(pmax)) {
                throw "跳塔参数" + param + "必须为最大dodge等级"
            }
            max = pmax
      }
      let skill = App.Core.PlayerGetSkillByID("dodge")
      if (skill != null) {
            if (max <= 0) {
                max = skill.Max
            }
            if (skill.Level >= max) {
                App.Fail()
                return
            }
      }
      App.Commands([
            App.NewCommand('prepare', App.PrapareFull),
            App.NewCommand("to", App.Options.NewWalk("sls-zlxy")),
            App.NewCommand("nobusy"),
            App.NewCommand("function", App.Quest.Tiaota.Tiao),
            App.NewCommand('do', "skills"),
            App.NewCommand("nobusy"),
      ]).Push()
      App.Next()
    }
    App.Quest.Tiaota.Tiao = function () {
      App.Quest.Tiaota.Data.Count++
      if (App.Quest.Tiaota.Data.Count > Round) {
            App.Next()
            return
      }
      if (!App.Stopped) {
            App.Commands([
                App.NewCommand("move", App.Options.NewPath("enter;u;u;u;u;u;u;out")),
                App.NewCommand("nobusy"),
                App.NewCommand("function", App.Quest.Tiaota.Tiao),
            ]).Push()
      }
      App.Next()
    }
})(App)App.Commands就是一个类似于Promise的结构。

App.Next()就是执行队列。

有个App.Fail()就是进行失败处理。

并不需要async机制。在mush里也可以用lua/js/python跑出来。

zhuzi 发表于 2023-6-6 21:49:15

高手论战
已star,持续关注
页: 1 [2] 3
查看完整版本: [PyMud]地图高级技巧-地形匹配与惯性导航在北侠中的实现