守望者--AIR技术交流

 找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

搜索
热搜: ANE FlasCC 炼金术
查看: 1009|回复: 1

[ActionScript] AS3多线程快速入门(一):Hello World

[复制链接]
  • TA的每日心情
    擦汗
    2018-4-10 15:18
  • 签到天数: 447 天

    [LV.9]以坛为家II

    1742

    主题

    2094

    帖子

    13万

    积分

    超级版主

    Rank: 18Rank: 18Rank: 18Rank: 18Rank: 18

    威望
    562
    贡献
    29
    金币
    51817
    钢镚
    1422

    开源英雄守望者

    发表于 2015-12-5 13:40:40 | 显示全部楼层 |阅读模式
    本帖最后由 破晓 于 2015-12-9 10:58 编辑



    随着AIR3.4和Flash Player11.4的测试版发布,Adobe终于推出了多年来被要求最多的API:多线程!
    如今,使用AS3 Workers让创建真正的多线程应用变得非常简单,只需要几行代码即可。
    这个API相当的简单易懂,而且他们还做了一些非常有用的事:比如ByteArray.shareable这个属性,能实现在worker之间共享内存;还有一个新的BitmapData.copyPixelsToByteArray方法,用来快速转换bitmapData到ByteArray。
    在本文中,我将全部过一遍Workers这个API的各个组成部分。然后我们再看一个简单的小程序:HelloWorker。
    如果你想要跟着做,你可以先下载下面的Flash Builder项目文件:

    FlashBuilder项目文件    (见附件)

    如果你只是想看看代码而已,废话就不多说了,下载这个看看:HellWorldWorker.as


    1. package
    2. {
    3.         import flash.display.Sprite;
    4.         import flash.events.Event;
    5.         import flash.system.MessageChannel;
    6.         import flash.system.Worker;
    7.         import flash.system.WorkerDomain;
    8.         import flash.utils.setInterval;
    9.         
    10.         public class HelloWorldWorker extends Sprite
    11.         {
    12.                 protected var mainToWorker:MessageChannel;
    13.                 protected var workerToMain:MessageChannel;
    14.                
    15.                 protected var worker:Worker;
    16.                
    17.                 public function HelloWorldWorker()
    18.                 {
    19.                         /**
    20.                          * Start Main thread
    21.                          **/
    22.                         if(Worker.current.isPrimordial){
    23.                                 //Create worker from our own loaderInfo.bytes
    24.                                 worker = WorkerDomain.current.createWorker(this.loaderInfo.bytes);
    25.                                 
    26.                                 //Create messaging channels for 2-way messaging
    27.                                 mainToWorker = Worker.current.createMessageChannel(worker);
    28.                                 workerToMain = worker.createMessageChannel(Worker.current);
    29.                                 
    30.                                 //Inject messaging channels as a shared property
    31.                                 worker.setSharedProperty("mainToWorker", mainToWorker);
    32.                                 worker.setSharedProperty("workerToMain", workerToMain);
    33.                                 
    34.                                 //Listen to the response from our worker
    35.                                 workerToMain.addEventListener(Event.CHANNEL_MESSAGE, onWorkerToMain);
    36.                                 
    37.                                 //Start worker (re-run document class)
    38.                                 worker.start();
    39.                                 
    40.                                 
    41.                                 //Set an interval that will ask the worker thread to do some math
    42.                                 setInterval(function(){
    43.                                         mainToWorker.send("ADD");
    44.                                         mainToWorker.send(2);
    45.                                         mainToWorker.send(2);
    46.                                 }, 1000);
    47.                                        
    48.                         }
    49.                         /**
    50.                          * Start Worker thread
    51.                          **/
    52.                         else {
    53.                                 
    54.                                 //Inside of our worker, we can use static methods to
    55.                                 //access the shared messgaeChannel's
    56.                                 mainToWorker = Worker.current.getSharedProperty("mainToWorker");
    57.                                 workerToMain = Worker.current.getSharedProperty("workerToMain");
    58.                                 //Listen for messages from the mian thread
    59.                                 mainToWorker.addEventListener(Event.CHANNEL_MESSAGE, onMainToWorker);
    60.                         }
    61.                 }
    62.                                 
    63.                 //Main >> Worker
    64.                 protected function onMainToWorker(event:Event):void {
    65.                         var msg:* = mainToWorker.receive();
    66.                         //When the main thread sends us HELLO, we'll send it back WORLD
    67.                         if(msg == "HELLO"){
    68.                                 workerToMain.send("WORLD");
    69.                         }
    70.                         else if(msg == "ADD"){
    71.                                 //Receive the 2 numbers and add them together
    72.                                 var result:int = mainToWorker.receive() + mainToWorker.receive();
    73.                                 //Return the result to the main thread
    74.                                 workerToMain.send(result);
    75.                         }
    76.                 }
    77.                
    78.                 //Worker >> Main
    79.                 protected function onWorkerToMain(event:Event):void {
    80.                         //Trace out whatever message the worker has sent us.
    81.                         trace("[Worker] " + workerToMain.receive());
    82.                 }
    83.         }
    84. }
    复制代码


    那Worker是什么呢?
    简单说,Worker就是在你主SWF里运行的另一个SWF程序。想要更深入一点的了解,请移步:Thibault Imbert的这篇牛文
    要创建一个worker,你需要调用WorkerDomain.current.createWorker()方法并传入一个SWF文件的字节流。
    共有3种方式可以生成那些SWF字节流。
    1.使用主SWF应用程序自身的loaderInfo.bytes属性,然后在文档类的构造函数里判断下Worker.current.isPrimordial标志(true代表当前是主线程)。这是创建Worker最快的方式,而且在即时调试周期上也有优势。
    2.发布一个SWF文件,然后用[Embed]标签嵌入它到你的主程序。这种方式在Flash Builder4.7里将会有工具支持,但在工具还没出来前这种方式还是太不灵活了。
    每次改变worker里的代码,你都必须重新导出一次SWF,这会让人很快就厌烦的!

    使用Worker From Class,这个新的类库能让你直接从类创建workers。这看起来似乎是最优的解决方案,但是它让你的项目引入了一系列的外部依赖。
    在这个初始的教程中,我将集中精力在第一种方式上,使用自身的loaderInfo.bytes字节流。
    这是一个快速但有一定瑕疵的方式。在接下来的教程中,我将试试“Worker From Class”类库方式。
    对于第2种方式,已有一个非常好的教程,可以去看看Lee Brimelow的视频教程part 1part 2.

    让我们交流吧
    在任何多线程使用场景中通信都是关键。
    从一个线程直接传送内存数据到另一个线程的代价是昂贵的,而内存共享又必需细致设计和周全考虑才行。
    在实现Worker系统过程中,大多数的挑战都来自寻求一种正确的设计架构,能让数据在你的Worker之间彼此共享。
    为了帮我们解决这个问题,Adobe给了我们一些简单(但灵活)的方式来发送数据。

    (1)worker.setSharedProperty()和worker.getSharedProperty()
    这是传递数据最简单但是功能也最有限的方式。你可以调用worker.setSharedProperty(“key”, value)来设置数据,然后在另一边用WorkerDomain.current.getSharedProperty(“key”)来获取它们。示例代码:
    1. //在主线程里
    2. worker.setSharedProperty("foo", true);
    3.   
    4. //在worker线程里
    5. var foo:Boolean =
    6. Worker.current.getSharedProperty("foo");
    复制代码
    你在这里存储简单或者复杂对象都可以,但对于多数情况下,存储的数据都是被序列化过的,它并不是真的被共享着。
    如果一个数据对象在一边改变了,在另一边并不会同步更新,直到等你再次调用了set/get方法后它才会更新。
    有俩例外情况是能共享的,如果你传递的对象是ByteArray且shareable属性为true,或者是一个MessageChannel对象。
    巧合的是,这些正是其他两种通信方法


    (2)MessageChannel

    MessageChannels就像是一个worker到另一个的单向通行管道。它们结合使用了事件机制和简单队列系统。
    你在一端调用channel.send()方法,在另一端一个就会抛出一个Event.CHANNEL_MESSAGE事件。在事件的处理函数里,你可以调用channel.receive()来接收发送过来的数据。
    就像我提到的,这个方法类似队列。所以,你可以在两边send()或receive()多次。
    示例代码:
    1. //在主线程里
    2. mainToWorker.send("ADD");
    3. mainToWorker.send(2);
    4. mainToWorker.send(2);
    5.   
    6. //在worker线程里
    7. function onChannelMessage(event:Event):void {
    8.   
    9.     var msg:String =
    10. mainToWorker.receive();
    11.     if(msg
    12. == "ADD"){
    13.         var val1:int =
    14. workerToMain.receive();
    15.         var val2:int =
    16. workerToMain.receive();
    17.         var result:int =
    18. val1 + val2;
    19.          //发送计算结果给主线程
    20.         workerToMain(result);
    21.     }
    22.   
    23. }
    复制代码
    MessageChannels对象使用worker.setSharedProperty()来实现共享,所以你想在多少个worker之间共享它都是可以的。
    不过它们都只能有一个发送终点。这样,约定以它们的接收者来命名似乎不错。例如channelToWorker或者channelToMain。

    示例代码:
    1. //在主线程里
    2. workerToMain
    3. = worker.createMessageChannel(Worker.current);
    4. worker.setSharedProperty("workerToMain",
    5. workerToMain);
    6.   
    7. mainToWorker
    8. = Worker.current.createMessageChannel(worker);
    9. worker.setSharedProperty("mainToWorker",
    10. mainToWorker);
    11.   
    12.   
    13. //在worker线程里
    14. workerToMain
    15. = Worker.current.getSharedPropert("workerToMain");
    16. mainToWorker=
    17. Worker.current.getSharedPropert("mainToWorker");
    复制代码
    对于MessageChannel和sharedProperties通信方式都有一个重要的限制,当数据发送时它们都被序列化了。
    这意味它们需要被解析,传输然后在另一端还原。这个的性能开销是比较大的。
    由于这个限制,这两种通信方式最好是应用于间隙性地传递小规模数据上。
    那如果你确实要共享一个庞大的数据块怎么办?答案是使用共享的ByteArray。


    (3)byteArray.shareable

    传输数据最快的方式就是根本不传输它!
    感谢Adobe给了我们可以直接共享ByteArray对象的途径。这是非常强大的,因为我们几乎可以在ByteArray里存储任何数据。
    要共享一个byteArray对象,你需要设置byteArray.shareable=true,然后调用messageChannel.send(byteArray)或者worker.setSharedPropert(“byteArray”, byteArray)方法来共享它。
    一旦你的byteArray对象是共享的,你可以直接对它写入数据,然后在另一端从它上面直接读取,太棒了.
    基本上这就是通信的所有方式了。正如你看到的,有3种截然不同的共享数据的方式。
    你可以用各种方式结合使用它们。已经过完了一遍基础结构,现在让我们来看看一个简单的Hello World的例子吧。

    示例应用程序

    首先,为了让你的工程能够正确编译运行,你需要做以下的事:
    1.下载最新的AIR3.4 SDK和playerglobal.swc文件,并确保配置进你的项目里。
    2.添加编译参数:"swf-version=17"到你的项目属性里。
    3.安装FlashPlayer 11.4 standalone debugger版本。
    做了以上步骤,你现在应该能看到各种Worker相关的API比如Worker,WorkerDomain,MessageChannel等等了。
    如果你在以步骤作中遇到困难,请参考Lee Brimelow的视频教程,最开始的几分钟有介绍这个。
    你也可以直接下载我Flash Builder项目的一个副本,应该能运行起来。


    第一步:文档类

    首先,我们需要创建我们的文档类:
    1. public class HelloWorldWorker extends Sprite{
    2.   
    3. protected var mainToWorker:MessageChannel;
    4. protected var workerToMain:MessageChannel;
    5. protected var worker:Worker;
    6.   
    7. public function HelloWorldWorker()
    8. {
    9.     /**
    10.      *
    11. 启动主线程
    12.      **/
    13.     if(Worker.current.isPrimordial){
    14.         //从我们自身的loaderInfo.bytes创建
    15.         worker
    16. = WorkerDomain.current.createWorker(this.loaderInfo.bytes);
    17.   
    18.         //为两个方向通信分别创建MessagingChannel
    19.         mainToWorker
    20. = Worker.current.createMessageChannel(worker);
    21.         workerToMain
    22. = worker.createMessageChannel(Worker.current);
    23.   
    24.         //注入MessagingChannel实例作为一个共享对象
    25.                   worker.setSharedProperty("mainToWorker",
    26. mainToWorker);
    27.         worker.setSharedProperty("workerToMain",
    28. workerToMain);
    29.   
    30.         //监听来自Worker的事件
    31.         workerToMain.addEventListener(Event.CHANNEL_MESSAGE,
    32. onWorkerToMain);
    33.   
    34.         //启动worker(重新实例化文档类)
    35.         worker.start();
    36.   
    37.         //设置时间间隔给worker发送消息
    38.         setInterval(function(){
    39.             mainToWorker.send("HELLO");
    40.             trace("[Main]
    41. HELLO");
    42.         }, 1000);
    43.   
    44.     }
    45.     else
    46.          {
    47.         //在worker内部,我们可以使用静态方法获取共享的messgaeChannel对象
    48.         mainToWorker
    49. = Worker.current.getSharedProperty("mainToWorker");
    50.         workerToMain
    51. = Worker.current.getSharedProperty("workerToMain");
    52.   
    53.         //监听来自主线程的事件        
    54.         mainToWorker.addEventListener(Event.CHANNEL_MESSAGE,
    55. onMainToWorker);
    56.     }
    57. }
    58. }
    复制代码
    跟着下面步骤一步一步来:
    1.我们使用Worker.current.isPrimordial属性来区分我们是主线程还是Worker。
    2.在主线程里,我们创建MessageChannels对象并且跟worker共享它。
    3.我们启动worker线程,这导致文档类再次被实例化。
    4.worker被创建了,然后获取共享的MessageChannels对象的引用。
    我还设置了一个小循环:每隔1000ms给worker发送一个"HELLO"字符串信息。
    接下来,我们将让worker返回一个“WORLD”给主线程并打印出来。

    第二步:处理事件

    现在我们已经有了共享的MessageChannels对象,我们可以轻松地在worker之间通信了。
    你可以看到在上面的代码中,我们已经给对象引用设置了事件监听了。
    所以剩下的事就是创建它们:
    1. //从主线程接收信息
    2. protected function onMainToWorker(event:Event):void {
    3.     var msg:*
    4. = mainToWorker.receive();
    5.     //当主线程给我们发送"HELLO"时,我们返回"WORLD"
    6.     if(msg
    7. == "HELLO"){
    8.         workerToMain.send("WORLD");
    9.     }
    10. }
    11.   
    12. //从worker线程接收信息
    13. protected function onWorkerToMain(event:Event):void {
    14.     //打印输出worker里接收到的任何消息
    15.     trace("[Worker]
    16. " +
    17. workerToMain.receive());
    18. }
    复制代码
    如果你现在运行这个程序,将会看见每隔1000ms“HELLO”和“WORLD”就被打印一次。
    祝贺你完成了你的第一个多线程应用程序!

    第三步:再多一些?

    好吧,不得不承认,这个程序看起来有点没啥用处。
    让我们稍微加强下我们的测试程序,让它做一些数学运算。
    2+2的咋样?首先,让我们修改interval函数,让它看起来像这样:
    1. //设置一个时间间隔让Worker线程做一些数学计算
    2. setInterval(function(){
    3.     mainToWorker.send("ADD");
    4.     mainToWorker.send(2);
    5.     mainToWorker.send(2);
    6.         trace("[Main]
    7. ADD 2 + 2?");
    8. }, 1000);
    复制代码
    然后我们再修改worker里的事件监听函数,来获取这些值:
    1. protected function onMainToWorker(event:Event):void {
    2.     var msg:*
    3. = mainToWorker.receive();
    4.     if(msg
    5. == "ADD"){
    6.         //接收到两个值,然后把它们相加
    7.                 var val1:int =
    8. mainToWorker.receive();
    9.                 var val2:int =
    10. mainToWorker.receive();
    11.         //返回计算结果给主线程
    12.         workerToMain.send(val1
    13. + val2);
    14.     }
    15. }
    复制代码
    就是这样!如果你现在运行这个程序,你将会看见“[Main] ADD 2 + 2?”,然后正确答案“4″会被输出。
    如果你想要查看完整的类。点击此处:HellWorldWorker.as在下一篇教程中,我们将一步一个脚印,看看如何利用多线程做一些图像处理的事。




    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?立即注册

    x
    守望者AIR技术交流社区(www.airmyth.com)
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2019-2-24 01:34
  • 签到天数: 3 天

    [LV.2]偶尔看看I

    0

    主题

    38

    帖子

    868

    积分

    上士

    Rank: 5Rank: 5

    威望
    20
    贡献
    0
    金币
    14
    钢镚
    0
    发表于 2019-2-24 03:32:44 | 显示全部楼层
    感谢分享!~
    守望者AIR技术交流社区(www.airmyth.com)
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    
    关闭

    站长推荐上一条 /4 下一条

    QQ|手机版|Archiver|网站地图|小黑屋|守望者 ( 京ICP备14061876号

    GMT+8, 2019-12-7 04:48 , Processed in 0.048303 second(s), 33 queries .

    守望者AIR

    守望者AIR技术交流社区

    本站成立于 2014年12月31日

    快速回复 返回顶部 返回列表