• Blog
  • Info Support
  • Career
  • Training
  • International Group
  • Info Support
  • Blog
  • Career
  • Training
  • International Group
  • Search
logo InfoSupport
  • Latest blogs
  • Popular blogs
  • Experts
      • All
      • Bloggers
      • Speakers
  • Meet us
  • About us
    • nl
    • en
    • .NET
    • 3D printing
    • Advanced Analytics
    • Agile
    • Akka
    • Alexa
    • Algorithms
    • Api's
    • Architectuur
    • Artificial Intelligence
    • ATDD
    • Augmented Reality
    • AWS
    • Azure
    • Big Data
    • Blockchain
    • Business Intelligence
    • Chatbots
    • Cloud
    • Code Combat
    • Cognitive Services
    • Communicatie
    • Containers
    • Continuous Delivery
    • CQRS
    • Cyber Security
    • Dapr
    • Data
    • Data & Analystics
    • Data Science
    • Data Warehousing
    • Databricks
    • DataOps
    • Developers life
    • DevOps
    • Digital Days
    • Digital Twin
    • Docker
    • eHealth
    • Enterprise Architecture
    • Event Sourcing
    • Hacking
    • Infrastructure & Hosting
    • Innovatie
    • Integration
    • Internet of Things
    • Java
    • Machine Learning
    • Microservices
    • Microsoft
    • Microsoft Bot Framework
    • Microsoft Data Platform
    • Mobile Development
    • Mutation Testing
    • Open source
    • Pepper
    • Power BI
    • Privacy & Ethiek
    • Python
    • Quality Assistance & Test
    • Quality Assurance & Test
    • Requirements Management
    • Scala
    • Scratch
    • Security
    • SharePoint
    • Software Architecture
    • Software development
    • Software Factory
    • SQL Server
    • SSL
    • Start-up
    • Startup thinking
    • Stryker
    • Test Quality
    • Testing
    • TLS
    • TypeScript
    • Various
    • Web Development
    • Web-scale IT
    • Xamarin
    • All
    • Bloggers
    • Speakers
Home » Flex 4: A multi threading solution
  • Flex 4: A multi threading solution

    • By Alex van Beek
    • Java 13 years ago
    • Java 0 comments
    • Java Java
    Flex 4: A multi threading solution

    If you develop applications in Silverlight a lot, it becomes more and more annoying that Flex doesn’t support multi threading. This is particularly annoying when you are processing a long running task and need to display some progress. The problem is that you can’t do this task on a background thread, this means that the ui thread is busy processing your task and it can’t update the ui until your task is done, at which the progress of course is 100%. In this article I’m going to show how you can somewhat work around Flex’ and more precisely Flash’ lack of multithreading while still being able to let the ui be responsive and show progress. 

    The sample Flex application

    The sample application in this blog post is a Flex application which turns an image into greyscale. A better option for this would be to use a custom filter built with PixelBender, but this example demonstrates the problem quite well and with PixelBender you lose the ability to report progress. Take a look at the screenshot below:

    image

    It has a nice photograph of a parrot, two buttons and a progressbar showing progress when the image is being converted to greyscale. Next up, the code behind:

       1: <?xml version="1.0" encoding="utf-8"?>

       2: <s:Application >"http://ns.adobe.com/mxml/2009" 

       3:                >"library://ns.adobe.com/flex/spark" 

       4:                >"library://ns.adobe.com/flex/halo" creationComplete="saveOriginalImageData(event)"

       5:               >

       6:     

       7:     

       8:     <fx:Script>

       9:         <![CDATA[

      10:             

      11:             private var _originalImageData : BitmapData;

      12:&nbsp; 

      13:             private function greyWithPauseHandler(event:MouseEvent):void

      14:             {

      15:                 var clone : BitmapData = _originalImageData.clone();

      16:                 _parrot.source = new Bitmap(clone);

      17:                 setProgress(0,0);

      18:                 convertToGreyScaleWithPauses(clone);

      19:             }

      20:         

      21:         

      22:                     

      23:             private function convertToGreyScale(toGreyScale: BitmapData) : void {

      24:                 var totalPixels: int = toGreyScale.width * toGreyScale.height;

      25:                 var amountForProgressUpdate : int = totalPixels / 16;

      26:                 setProgress(0, totalPixels);

      27:                 

      28:                 for(var i : int = 0; i < totalPixels; i++) {

      29:                     var y: int = i / toGreyScale.width;

      30:                     var x :int = i - y * toGreyScale.width;

      31:                     var pixel:uint = toGreyScale.getPixel(x,y);

      32:                     var red:uint = (pixel >> 16) & 255;

      33:                     var green:uint = (pixel >> 8) & 255;

      34:                     var blue:uint = pixel & 255;

      35:                     var grey:uint = (red * 0.3) + (green * 0.59) + (blue * 0.11);

      36:                     toGreyScale.setPixel(x,y,(grey<<16) | (grey<<8) | grey);

      37:                     

      38:                     if(i % amountForProgressUpdate == 0 && i != 0) {

      39:                         setProgress(i + 1, totalPixels);    

      40:                     }

      41:                 }

      42:                 setProgress(totalPixels, totalPixels);

      43:             }

      44:             private function convertToGreyScaleWithPauses(toGreyScale: BitmapData) : void {

      45:                 var totalPixels: int = toGreyScale.width * toGreyScale.height;

      46:                 setProgress(0, totalPixels);

      47:                 

      48:                     UIUtilities.pausingFor(0,totalPixels, function(i:int) : void {

      49:                         var y: int = i / toGreyScale.width;

      50:                         var x :int = i - y * toGreyScale.width;

      51:                         var pixel:uint = toGreyScale.getPixel(x,y);

      52:                         var red:uint = (pixel >> 16) & 255;

      53:                         var green:uint = (pixel >> 8) & 255;

      54:                         var blue:uint = pixel & 255;

      55:                         var grey:uint = (red * 0.3) + (green * 0.59) + (blue * 0.11);

      56:                         toGreyScale.setPixel(x,y,(grey<<16) | (grey<<8) | grey);

      57:                     },setProgress ,this);

      58:                     

      59:                     _parrot.source = new Bitmap(toGreyScale);

      60:             }

      61:             

      62:             private function setProgress(processed : int, amountThatNeedsToBeProcessed : int) : void {

      63:                 _progress.setProgress(processed, amountThatNeedsToBeProcessed);                

      64:             }

      65:&nbsp; 

      66:&nbsp; 

      67:             private function greyHandler(event:MouseEvent):void

      68:             {

      69:                 var clone : BitmapData = _originalImageData.clone();

      70:                 _parrot.source = new Bitmap(clone);

      71:                 setProgress(0,0);

      72:                 // Need an artificial delay (Timer) to let the Image update itself and show the original image before 

      73:                 // the greying begins...

      74:                 convertToGreyScale(clone);

      75:             }

      76:&nbsp; 

      77:             private function saveOriginalImageData(event:Event):void

      78:             {

      79:                 _originalImageData = Bitmap(_parrot.content).bitmapData;

      80:             }

      81:&nbsp; 

      82:         ]]>

      83:     </fx:Script>

      84:     

      85:     <mx:ProgressBar mode="manual" minimum="0" maximum="1000000000000" id="_progress" horizontalCenter="0" bottom="6" label="Greying: %3%%"/>

      86:     <mx:Image id="_parrot" source="@Embed('./assets/parrot_heada.jpg')" left="10" right="10" top="10" bottom="77" scaleContent="true"/>

      87:     <s:Button label="Grey it with pauses" horizontalCenter="-107" bottom="48" click="greyWithPauseHandler(event)"

      88:               />

      89:     <s:Button label="Grey it" bottom="48" horizontalCenter="65" click="greyHandler(event)"/>

      90:     

      91: </s:Application>

    At this point I encourage you to download the sample project here and run it. If you don’t have Flash Builder installed, I’ve exported a release build here which you can run by double clicking the .html file.&nbsp;First click on the “Grey it&rdquo; button, you&rsquo;ll see the ui freeze for a while, when the application has finished converting the image to greyscale, the ui unfreezes and the progressbar jumps to 100%. Next, click on the “Grey it with pauses&rdquo; button. You&rsquo;ll see the image being converted to greyscale gradually and you&rsquo;ll see the progressbar showing progress, you&rsquo;ll also notice that the “Grey it with pauses&rdquo; takes more time to complete than the “Grey it&rdquo; button. Let&rsquo;s explain what happens:

    • Take a look at line 67, this method get&rsquo;s called when the “Grey it&rdquo; button is clicked. It clones the original BitmapData (which gets saved on the creationComplete event), resets the progress method using the “setProgress()&rdquo; method on line 62 and calls the “convertToGreyScale()” method which does all the work. You can see a problem indicated in the comment. The problem is that when supplying the “_parrot&rdquo; Image component with the original colored image data, the user interface doesn&rsquo;t update until the “convertToGreyScale()&rdquo; method has completed. The only way to work around this is to build in an artificial delay using a Timer for example and experiment to find the correct delay in which the Image get&rsquo;s updated. That&rsquo;s why I previously told you to first click on the “Grey it&rdquo; button and then on the “Grey it with pauses&rdquo; button. The other way around you don&rsquo;t see the image going from colored to greyscale.
    • In the “convertToGreyScale()&rdquo; method on line 23, the BitmapData data is processed and converted to grayscale using a standard algorithm, maybe it&rsquo;s not the most optimized algorithm, but it suffices. Most people would use nested for loops, I&rsquo;ve converted it to a single loop in order to explain the next part more easily. This method updates the progressbar once in a while, in total 16 times, this is determined on line 25. If I updated the progressbar every iteration, I would get a script timeout, even if this timeout was set to 60 seconds, which is the maximum. As you&rsquo;ve seen, the user only sees the progressbar at 100% when this method is finished, because only then can Flash update the ui. In fact, all the calls to “setProgress()” could be removed from this function, because they don&rsquo;t have any visual effect.
    • Next take a look at the “convertToGreyScaleUsingPauses()&rdquo; method on line 44. This method get&rsquo;s called when the user clicks the “Grey it with pauses&rdquo; button. The heart of this method is on line 48. The “UIUtilities.pausingFor()&rdquo; method is called, in almost the same way as I configured the for loop in the “convertToGreyScale()&rdquo; method. The loop body has become an anonymous function that must accept an int (the current iteration) and is called multiple times depending on the first two arguments. Next to the first anonymous function I supply another function which is responsible for setting the progress. The “pausingFor&rdquo; function makes sure that pauses occur, in which the ui can update itself, hence the name “pausingFor&rdquo;. Take a look at the code below:
       1: package

       2: {

       3:     import mx.core.UIComponent;

       4:&nbsp; 

       5:     public final class UIUtilities

       6:     {

       7:         

       8:         /**

       9:          * Executes a for loop that pauses once in a while to let the UI update itself. The parameters of this method

      10:          * are derived from a normal 

      11:          * for(var i :int =0; i <10;i++) {

      12:          * }

      13:          * loop.

      14:          * @param fromInclusive The 0 in the loop above.

      15:          * @param toExclusive The 10 in the loop above.

      16:          * @param loopBodyFunction The loop body, everything between {} in the loop above. This function must accept an int,

      17:          * which represents the current iteration.

      18:          * @param updateProgressFunction The method that needs to be called to update the UI, for example a progressbar.

      19:          * this method must accept two ints, The first is the number of iterations processed, the other is the total number of

      20:          * of iterations that need to be processed.

      21:          * @param componentInDisplayList Any component that is connected to the displaylist. This method makes use

      22:          * of the callLater() method which is available on any UIComponent. The root Application is an easy choice.

      23:          * @param numberOfPauses The number of times this method pauses to let the UI update itself.

      24:          * The correct amount is hardware dependent, 8 pauses doesn't mean you'll see 8 UI updates. Experiment

      25:          * to find the number that suits you best. A higher number means less performance, but more ui updates and

      26:          * visual feedback.

      27:          **/

      28:         public static function pausingFor(fromInclusive:int, toExclusive :int,loopBodyFunction : Function,updateProgressFunction : Function,componentInDisplayList:UIComponent,

      29:                                    numberOfPauses : int = 8) : void {

      30:             executeLoop(fromInclusive,toExclusive, toExclusive / numberOfPauses, loopBodyFunction,updateProgressFunction, componentInDisplayList)

      31:         }

      32:         

      33:     

      34:         private static function executeLoop(fromInclusive:int, toExclusive :int,numberOfIterationsBeforePause : int, loopBodyFunction : Function,

      35:                                              updateProgressFunction : Function,componentInDisplayList : UIComponent) : void {

      36:             var i : int = fromInclusive;

      37:             for(i; i < toExclusive;i++) {

      38:                 //determine the rest of the number of iterations processed and the numberOfIterationsBeforePause

      39:                 //This is needed to determine whether a pause should occur. 

      40:                 var rest : Number = i % numberOfIterationsBeforePause;

      41:                 

      42:                 //If the rest is 0 and i not is 0, a pause must occur to let the ui update itself

      43:                 if(rest == 0 && i != 0) {

      44:                     

      45:                     //use callLater to pause and let the UI update.....

      46:                     componentInDisplayList.callLater(

      47:                         //Supply anonymous function to the callLater method, which can be called after the pause...

      48:                         function(index:int) : void {

      49:                             //after pausing, resume work...

      50:                             loopBodyFunction(index);

      51:                             //We need to continue with the executeLoop() method. The current index has already

      52:                             //been processed so continue this method with the next index

      53:                             executeLoop(index + 1,toExclusive,numberOfIterationsBeforePause,loopBodyFunction,updateProgressFunction,componentInDisplayList);

      54:                         },[i]);

      55:                     //When using callLater to let the UI update, my own code must be finished. So break out of the loop 

      56:                     break;

      57:                 } else {

      58:                     //No time for a pause

      59:                     loopBodyFunction(i);

      60:                     //Just before a pause occurs, report progress so that a user can set progress values

      61:                     if(rest == numberOfIterationsBeforePause - 1) {

      62:                         updateProgressFunction(i + 1, toExclusive);

      63:                     }

      64:                 }

      65:             }

      66:             //Final progress update

      67:             updateProgressFunction(i + 1, toExclusive);

      68:         } 

      69:         

      70:     }

      71: }

    This is the source code of the UIUtilities class. The only public static function is the “pausingFor&rdquo; function. Take your time reading the ASDoc, it explains what this function does and what the different parameters are for. This method calls another private static function “executeLoop()&rdquo; which has almost the same parameters as the “pausingFor&rdquo; method, except for the “numberOfIterationsBeforePause&rdquo; parameter. This parameter is the result of the “toExlusive&rdquo; parameter divided by the “numberOfPauses&rdquo; parameter of the “pausingFor&rdquo; method.

    Let’s take a look the “executeLoop()” method on line 34. I&rsquo;ve written inline comments that explain most of what&rsquo;s going on. The most important part of this function is on line 46. This is the “callLater()&rdquo; method that any UIComponent has and this is one of the most overlooked methods in Flex. It accepts a function as the first parameter and an array with arguments to supply to that function as the second parameter. When you call this method, Flex schedules the supplied function to be called in the next frame. This way, the ui can update itself for the rest of the duration of the current frame. This means that there must be enough time left in the current frame and there mustn’t be any of your own code that needs to be executed after the call to “callLater()&rdquo; , or else you still won&rsquo;t see any ui updates. In the example above I supply an anonymous function that needs to be called in the next frame. In this anonymous function I do two things:

    • Let the “loopBodyFunction” parameter execute.
    • Afterwards call the “executeLoop()” function again. This way I&rsquo;m sure that after the ui has had time to update itself, the&nbsp; “executeLoop()” function resumes with the correct iteration after being suspended by the “callLater()” method.

    Another interesting part is on line 61. In the iteration just before the iteration in which the “callLater()” function is called, I give the user the chance to set any progress values that the ui needs for updating itself, by calling the “updateProgressFunction” parameter and supplying the number of iterations processed and the number of iterations that need to be processed. This way, I minimize the calls to the ui to when they matter the most: just before a call to “callLater()”.&nbsp;

    Conclusion

    With my “UIUtilities.pausingFor()” function, I&rsquo;ve shown how you can let the ui still be responsive and let it update itself, in a way that&rsquo;s quite similar to using a normal “for” loop in a background thread. My “UIUtilities” class&nbsp; can also be easily expanded with a “pausingForEach()” function, building on the “pausingFor” function. While the api of the “UIUtilities” class is quite simple, be aware that using this api has a performance cost, letting the task finish without giving the ui the time to update itself, is always faster, but not necessarily better for the user of the application. Also, this class is nowhere near a replacement for having the ability to do true multi threading, which would be better for the performance, certainly on multi core processors. But until we have that possibility in Flash, we have to work around the lack of multi threading using solutions similar to the solution provided above. While my post was titled with “Flex 4”, the solution provided above should also work on Flex 3. You can find the working solution with the source code, here.

    Share this

Alex van Beek

View profile

Related IT training

Go to training website

Related Consultancy solutions

Go to infosupport.com

Related blogs

  • It’s Java 20 Release Day! Here’s What’s New

    It’s Java 20 Release Day! Here’s What’s New Hanno Embregts - 1 week ago

  • Exploring sustainability in tech (without the guilt-tri…

    Exploring sustainability in tech (without the guilt-tri… Hanno Embregts - 4 months ago

  • Eleven crazy learnings from the Java 11 certification: …

    Eleven crazy learnings from the Java 11 certification: … Hanno Embregts - 6 months ago

Data Discovery Channel

  • Explainable AI - Break open the blackbox

  • Toekomstvaste microservice data architecturen

  • Modern Data Platform

Nieuwsbrief

* verplichte velden

Contact

  • Head office NL
  • Kruisboog 42
  • 3905 TG Veenendaal
  • T +31 318 552020
  • Call
  • Mail
  • Directions
  • Head office BE
  • Generaal De Wittelaan 17
  • bus 30 2800 Mechelen
  • T +32 15 286370
  • Call
  • Mail
  • Directions

Follow us

  • Twitter
  • Facebook
  • Linkedin
  • Youtube

Newsletter

Sign in

Extra

  • Media Library
  • Disclaimer
  • Algemene voorwaarden
  • ISHBS Webmail
  • Extranet
Beheer cookie toestemming
Deze website maakt gebruik van Functionele en Analytische cookies voor website optimalisatie en statistieken.
Functioneel Always active
De technische opslag of toegang is strikt noodzakelijk voor het legitieme doel het gebruik mogelijk te maken van een specifieke dienst waarom de abonnee of gebruiker uitdrukkelijk heeft gevraagd, of met als enig doel de uitvoering van de transmissie van een communicatie over een elektronisch communicatienetwerk.
Voorkeuren
De technische opslag of toegang is noodzakelijk voor het legitieme doel voorkeuren op te slaan die niet door de abonnee of gebruiker zijn aangevraagd.
Statistieken
De technische opslag of toegang die uitsluitend voor statistische doeleinden wordt gebruikt. De technische opslag of toegang die uitsluitend wordt gebruikt voor anonieme statistische doeleinden. Zonder dagvaarding, vrijwillige naleving door uw Internet Service Provider, of aanvullende gegevens van een derde partij, kan informatie die alleen voor dit doel wordt opgeslagen of opgehaald gewoonlijk niet worden gebruikt om je te identificeren.
Marketing
De technische opslag of toegang is nodig om gebruikersprofielen op te stellen voor het verzenden van reclame, of om de gebruiker op een website of over verschillende websites te volgen voor soortgelijke marketingdoeleinden.
Manage options Manage services Manage vendors Read more about these purposes
Voorkeuren
{title} {title} {title}