AS3多线程快速入门(二):图像处理[译]

摘要:
在本文中,我将进一步介绍如何使用多线程来实现一些有用的功能,例如图像处理!尤其是在处理多线程时,我们真的需要创建一个高效的系统来调度工人之间的数据。否则,由于序列化和反序列化数据的巨大成本,主线程将陷入麻烦。防止过度磨锐操作。下一步是通知工作人员在主类中锐化位图,并在完成任务时作出响应。

原文链接:http://esdot.ca/site/2012/intro-to-as3-workers-part-2-image-processing

在《AS3多线程快速入门》系列教程的第一部分中,我们研究了AS3 Worker的基本原理,包括多种通信方式,还展示了一个简单例子:Hello World Worker。

在这篇文章里,我将更进一步,向你展示如何利用多线程做些有用的功能,比如图像处理!在这次例子中,我将一边给一个大位图应用锐化滤镜,一边让主UI线程持续保持在30fps的渲染帧率。

演示:单线程版本

你可以在下面简单看看我们要做的是什么。这个是没有使用workers的版本,你可以看到:在位图处理过程中Slider完全锁定了无法移动。

注:如果你看不见这个SWF程序,检查下你是否已经下载了Flash Player11.4。如果使用的是谷歌浏览器,别忘了要禁用老版本的插件

演示:多线程版本

下面是同一个演示程序,但是使用了Worker。你可以看到它的UI渲染流畅地保持在30fps。

注:如果你看不见这个SWF程序,检查下你是否已经下载了Fash Player11.4。如果使用的是谷歌浏览器,别忘了要禁用老版本的插件

代码

在实际开始写代码前,未雨绸缪下是很重要的。尤其是在处理多线程时,我们确实需要创建一个在worker之间调度数据的高效系统。否则,你的主线程将会因为序列化和反序列化数据造成的巨大开销而陷入困境。

经过一点思考,我决定把这个程序设计成这样:

1.bitmapData通过一个shareable为true的byteArray对象来与worker共享。
2.我们使用bitmapData.setPixels()和bitmapData.copyPixelsToByteArray()方法在bitmapData和byteArray之间来回转换。
3.主线程发出”SHARPEN”命令给worker,然后worker线程在完成时将返回”SHARPEN_COMPELETE”命令。
4.worker线程将使用一个间隔500ms的timer来检测是否需要重新启动一个锐化操作。防止运行过度的锐化操作。

文档类代码

首先是类结构,这里我们将使用和上一个教程里一样方式:用loaderInof.bytes。这个构造函数会被运行两次,第二次运行的是worker,它会创建一个SharpenWorker类的实例,这个实例将负责处理所有与主线程的通信。

1
2
3
4
5
6
7
8
9
10
11
12
13
publicclassImageWorkerExample extendsSprite
{
publicfunctionImageWorkerExample(){
    //主线程
    if(Worker.current.isPrimordial){
        initUi();
        initWorker();
    }
    //如果不是主线程,就是worker
    else{
        sharpenWorker = newSharpenWorker();
    }
}

在看SharpenWorker类之前,我们先继续读主类。

initUi()方法只是简单地创建了一个slider,一个image,并且添加他们到舞台。这里不需要去关注它。

下一个方法是initWorker(),可以看看代码内的注释。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
protectedfunctioninitWorker():void{
    //从主swf里创建worker
    worker = WorkerDomain.current.createWorker(loaderInfo.bytes);
 
    //创建到worker的MessageChannel通信对象
    mainToBack = Worker.current.createMessageChannel(worker);
 
    //创建来自worker的MessageChannel通信对象并添加监听.
    backToMain = worker.createMessageChannel(Worker.current);
    backToMain.addEventListener(Event.CHANNEL_MESSAGE, onBackToMain, false, 0, true);
 
    //现在我们有两个通信对象,把它们作为共享属性注入到worker线程
    //这样,worker线程就能在另一边获取它们
    worker.setSharedProperty("backToMain", backToMain);
    worker.setSharedProperty("mainToBack", mainToBack);
 
    //给worker传递初始化图像宽高尺寸
    worker.setSharedProperty("imageWidth", origImage.width);
    worker.setSharedProperty("imageHeight", origImage.height);
 
    //转换位图数据并存储到共享的byteArray对象里,与worker线程共享。
    imageBytes = newByteArray();
    imageBytes.shareable = true;
    origImage.copyPixelsToByteArray(origImage.rect, imageBytes);
    worker.setSharedProperty("imageBytes", imageBytes);
 
    //最后,启动worker线程.
    worker.start();
}

在以上代码中,我们做了这些事:

1.使用我们自身的loaderInfo.bytes创建了worker。
2.创建了MessageChannel对象,让主线程和worker线程之间能互相通信。
3.复制位图数据到共享的byteArray对象内。
4.与worker线程共享位图数据。
5.启动worker线程。

下一步操作是在主类里通知worker去锐化位图,并且在完成任务时响应。

首先,我们要添加Slider的CHANGE事件处理函数:

1
2
3
4
5
protectedfunctiononSliderChanged(value:Number):void{
    //给我们的worker线程发送锐化命令.
    mainToBack.send("SHARPEN");
    mainToBack.send(value);
}

然后,我们添加worker的完成任务时的响应函数。

1
2
3
4
5
6
7
protectedfunctiononBackToMain(event:Event):void{
    varmsg:String= backToMain.receive();
    if(msg == "SHARPEN_COMPLETE"){
        imageBytes.position = 0;
        image.bitmapData.setPixels(image.bitmapData.rect, imageBytes);
    }
}

以上就完成主类的代码了!

Worker代码

worker类的结构非常简单明了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
publicclassSharpenWorker extendsSprite
{
publicfunctionSharpenWorker(){
    //获取当前worker线程的引用(自身)
    varworker:Worker = Worker.current;
 
    //监听mainToBack的SHARPEN事件
    mainToBack = worker.getSharedProperty("mainToBack");
    mainToBack.addEventListener(Event.CHANNEL_MESSAGE, onMainToBack);
    //使用backToMain抛出SHARPEN_COMPLETE命令
    backToMain = worker.getSharedProperty("backToMain");
 
    //从共享属性缓存池里获取位图数据。
    imageBytes = worker.getSharedProperty("imageBytes");
    varw:int= worker.getSharedProperty("imageWidth");
    varh:int= worker.getSharedProperty("imageHeight");
 
    imageBytes.position = 0;
    imageData = newBitmapData(w, h, false, 0x0);
    backToMain.send(imageBytes.length);
    imageData.setPixels(imageData.rect, imageBytes);
 
    //创建计时器间隔检测锐化值是否已经改变了
    timer = newTimer(500);
    timer.addEventListener(TimerEvent.TIMER, onTimer, false, 0, true);
    timer.start();
}

这里我们做了这些事:

1.持有主线程共享的MessageChanel对象引用。
2.通过共享的byteArray对象数据初始化bitmapData。
3.在内部存储byteArray对象的引用,这样我们从现在开始就可以往里面写入数据了。
4.创建一个Timer来检测是否需要重新锐化。

接下来,我们需要响应SHARPEN请求。这在mainToBack的监听函数里做了处理:

1
2
3
4
5
6
7
8
9
10
11
protectedfunctiononMainToBack(event:Event):void{
    if(mainToBack.messageAvailable){
        //获取消息类型
        varmsg:* = mainToBack.receive();
        //锐化
        if(msg == "SHARPEN"){
            targetSharpen = mainToBack.receive();
        }
 
    }
}

注意下在这里我们实际上除了存储下锐化值并没有做任何事。如果每一次请求我们都立即应用锐化操作将会非常浪费性能。锐化操作需要超过500ms时间才能完成,但是主线程会以33ms的间隔发送锐化命令。所以很明显,如果我们响应每次请求将会造成巨大的堵塞。最终你的worker线程会崩溃。

相反,我们在时间间隔500ms的TIMER事件处理函数里应用锐化滤镜:

1
2
3
4
5
6
7
8
9
10
11
12
protectedfunctiononTimer(event:TimerEvent):void{
    if(targetSharpen == currentSharpen){ return; } //直到锐化值改变才启动锐化
    currentSharpen = targetSharpen;
 
    //锐化位图并复制它到byteArray对象里
    vardata:BitmapData = ImageUtils.SharpenImage(imageData, currentSharpen);
    imageBytes.length = 0;
    data.copyPixelsToByteArray(data.rect, imageBytes);
 
    //通知主线程锐化操作已经完成
    backToMain.send("SHARPEN_COMPLETE");
}

这就是所有的代码了。主线程将会发送附带锐化值的SHARPEN命令,worker线程获取到它后,更新共享的byteArray对象数据并发回SHARPEN_COMPLETE命令。一旦接收到SHARPEN_COMPLETE命令,主线程将会使用共享的byteArray数据更新位图显示对象。

注:如果你对锐化操作本身比较感兴趣,这个优秀的滤镜来自gskinner.com.

你可以在这里下载到完整的测试代码工程:

ImageWorkerExample(包括没有多线程的代码例子)

多线程使用愉快!

来自:http://blog.domlib.com/articles/326.html

免责声明:文章转载自《AS3多线程快速入门(二):图像处理[译]》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇安装mingw若干结论和定理(停更)下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

iOS UI-线程(NSThread)及其安全隐患与通信

一、基本使用 1.多线程的优缺点 多线程的优点 能适当提高程序的执行效率 能适当提高资源利用率(CPU、内存利用率) 多线程的缺点 开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能 线程越多,CPU在调度线程上的开销就越大 程序设计更加复杂:比如线程之间的通信、多线...

学习Android camera笔记 & 调用流程

参考:http://blog.csdn.net/xingyu19871124/article/details/7750189http://blog.csdn.net/BonderWu/article/details/5814278http://blog.chinaunix.net/uid-2630593-id-3307176.htmlhttp://zhid...

python基础整理5——多进程多线程和协程

进程与线程 1.进程 我们电脑的应用程序,都是进程,假设我们用的电脑是单核的,cpu同时只能执行一个进程。当程序处于I/O阻塞的时候,CPU如果和程序一起等待,那就太浪费了,cpu会去执行其他的程序,此时就涉及到切换,切换前要保存上一个程序运行的状态,才能恢复,所以就需要有个东西来记录这个东西,就可以引出进程的概念了。 进程就是一个程序在一个数据集上的一次...

面试官:Redis 单线程已经很快,为何 6.0要引入多线程?有啥优势?

作者:Java斗帝之路 链接:https://www.jianshu.com/p/ba2f082ff668 Redis作为一个基于内存的缓存系统,一直以高性能著称,因没有上下文切换以及无锁操作,即使在单线程处理情况下,读速度仍可达到11万次/s,写速度达到8.1万次/s。但是,单线程的设计也给Redis带来一些问题: 只能使用CPU一个核; 如果删除的键...

Android 异步框架 RxJava2

观察者模式的概念 RxJava是android的异步框架,官方介绍是可观测的序列,组成异步基于事件程序的库。特点是观察者模式,基于事件流的链式调用,随着异步操作调度过程复杂的情况下,程序逻辑也变得越来越复杂,但RxJava依然能够保持简洁。 简单的说观察者A与被观察者B建立订阅关系,当被观察者B发生某种改变时,立即通知观察者A 添加依赖 compile '...

C# 多线程控制控件实例

该实例功能为“多线程控制UI控件”,线程函数实现自动加1。界面如下: using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Text;using System...