Posts Are async animations not working? It's easy and fun with F#
Post
Cancel

Are async animations not working? It's easy and fun with F#

Prerequisites

  • An understanding of Xamarin
  • An simple understanding of Threads
  • An understanding of F#

Recently I attended a Winter of Xamarin event hosted by Microsoft. It was a great event, but as always the code examples all in C#. I thought it would be a great a exercise to translate the C# app into F#. This post focuses on one particular section that I came across, some Xamarin Forms’ animations, that I found less than trivial to translate. The various attempts that I tried will be outlined, with the final solution at the end.

The animation code

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
    public async Task Shake()
    {
        for (int i = 0; i < 8; i++)
        {
            await Task.WhenAll(
                    this.ScaleTo(1.1, 20, Easing.Linear),
                    this.TranslateTo(-30, 0, 20, Easing.Linear)
                );

            await Task.WhenAll(
                    this.TranslateTo(0, 0, 20, Easing.Linear)
                );

            await Task.WhenAll(
                    this.TranslateTo(0, -30, 20, Easing.Linear)
                );

            await Task.WhenAll(
                this.ScaleTo(1.0, 20, Easing.Linear),
                this.TranslateTo(0, 0, 20, Easing.Linear)
            );
        }
    }

Here is the main gist of the code. The method is part of a ContentPage. I’m assuming that most readers will understanding this, so will only go over this briefly. Task.WhenAll executes the animations in parallel. The await after each Task.WhenAll means wait for it to complete, so the inside of the loop, contains 4 sequential animations. Finally the loop just runs the 4 animations, sequentially 8 times.

Async in F#

Understanding Async in F# can be a little tricky. This is not because is it complicated, rather it is more explicit. Tomas Petricek has a great ‘series on Async’, and another post on the ‘Async in C# and F#: gotchas in C#’. Armed with this knowledge here is my first attempt at translating the inner loop.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
// Nested inside a ContentPage
let show() = 
    [
        [|
            this.ScaleTo(1.1, 20u, Easing.Linear)
            this.TranslateTo(-30., 0., 20u, Easing.Linear)
        |]
        [| this.TranslateTo(0., 0., 20, Easing.Linear) |]
        [| this.TranslateTo(0., -30., 20u, Easing.Linear) |]
        [|
            this.ScaleTo(1.0, 20u, Easing.Linear)
            this.TranslateTo(0., 0., 20u, Easing.Linear)
        |]
    ] |> List.iter(fun animations -> 
        animations
        |> Array.map Async.AwaitTask 
        |> Async.Parallel 
        |> Async.Ignore 
        |> Async.StartImmediate)

Here’s some F# code that compiles and was my first attempt. Preferring a functional style, I wanted to treat the animations as data, and then iter (map with no result) over them. The function inside the iter contains the relevant transformations to make the code compile with F#. Let’s walk through it. First animations is an array of Task and F# needs Async, so lets map over them and convert them to Asyncs. Next we we want to run them in parallel, Async.Parallel for the job. This is the same as C#’s Task.WhenAll. On to the next line, F# checks return types, and each of the animations return a bool. Async.Ignore will fix up the return type since there is nothing to check. Finally, Async.StartImmediate starts on the main thread an returns immediately so it won’t block the main thread, but also doesn’t create a new thread. (Async.RunSynchronously can’t be used here since the animations must be run on the main thread and will wait for. The result: a deadlock). I’ll save you time of trying the show function out; it doesn’t work. When you run it, nothing happens. No crash, no animation.

But it compiled

When dealing with side effects, all bets are off as to whether the code works; animations are side effects. A careful read on Thomas’ blog can give us an understanding of what is happening here. On the C# side, static methods for Tasks start immediately. This means that treating animations as data in a list (array) won’t work as they will be started immediately. Armed with this knowledge here is another attempt.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
let shake () =  
    async {
        do! [|
                this.ScaleTo(1.1, 20u, Easing.Linear)
                this.TranslateTo(-30., 0., 20u, Easing.Linear)
            |] |> Array.map Async.AwaitTask |> Async.Parallel |> Async.Ignore
        do! [| 
                this.TranslateTo(0., 0., 20u, Easing.Linear) 
            |] |> Array.map Async.AwaitTask |> Async.Parallel |> Async.Ignore
        do! [| 
                this.TranslateTo(0., -30., 20u, Easing.Linear) 
            |] |> Array.map Async.AwaitTask |> Async.Parallel |> Async.Ignore
        do! [|
                this.ScaleTo(1.0, 20u, Easing.Linear) 
                this.TranslateTo(0., 0., 20u, Easing.Linear)
            |] |> Array.map Async.AwaitTask |> Async.Parallel |> Async.Ignore
    } |> Async.StartImmediate

This looks a little better and this does in fact work. The animation runs. As before, for each of the arrays, they need to be converted for Tasks, set to run in parallel and have their result ignored. This time, instead of putting all the arrays in a list, they are inside an async block, and the do! tells F# to wait for each one to finish before starting the next one. Async.StartImmediate starts this off nicely on the main thread. I’ll come back to this later and refactor out some of the duplicated code, but now let’s get this running 8 times.

Running 8 times

Surely it can’t be hard to run an animation 8 times. Also were using F# so variables and loops are beneath us. Here is my (failed) first attempt at running the above code 8 times.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
let shake () =  
    async {
        do! [|
                this.ScaleTo(1.1, 20u, Easing.Linear)
                this.TranslateTo(-30., 0., 20u, Easing.Linear)
            |] |> Array.map Async.AwaitTask |> Async.Parallel |> Async.Ignore
        do! [| 
                this.TranslateTo(0., 0., 20u, Easing.Linear) 
            |] |> Array.map Async.AwaitTask |> Async.Parallel |> Async.Ignore
        do! [| 
                this.TranslateTo(0., -30., 20u, Easing.Linear) 
            |] |> Array.map Async.AwaitTask |> Async.Parallel |> Async.Ignore
        do! [|
                this.ScaleTo(1.0, 20u, Easing.Linear) 
                this.TranslateTo(0., 0., 20u, Easing.Linear)
            |] |> Array.map Async.AwaitTask |> Async.Parallel |> Async.Ignore
    } |> List.replicate 8 |> List.iter (fun animation -> async {do! animation} |> Async.StartImmediate)

The last line is all that’s changed. An attempt was made to replicate the Async action 8 times and then iter over the resulting list wrapping the animation in do! and executing as Async.StartImmediate. I had assuming that Async.StartImmediate was the same as C# and that they could be chained together. As already stated, this doesn’t work. In F#, any async work must be enclosed in an async block, and started only once. Async.StartImmediate is analogous with an a C# method of public async void meaning you can not continue after the task has been started.

A working version

I still didn’t want to put a variable with a for loop in, so I found the best compromise I could handle. Here is the first working solution:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
let shakeLandscapeAsync () = 
    let shake =  async {
        do! [|
                this.ScaleTo(1.1, 20u, Easing.Linear)
                this.TranslateTo(-30., 0., 20u, Easing.Linear)
            |] |> Array.map Async.AwaitTask |> Async.Parallel |> Async.Ignore
        do! [| 
                this.TranslateTo(0., 0., 20u, Easing.Linear) 
            |] |> Array.map Async.AwaitTask |> Async.Parallel |> Async.Ignore
        do! [| 
                this.TranslateTo(0., -30., 20u, Easing.Linear) 
            |] |> Array.map Async.AwaitTask |> Async.Parallel |> Async.Ignore
        do! [|
                this.ScaleTo(1.0, 20u, Easing.Linear) 
                this.TranslateTo(0., 0., 20u, Easing.Linear)
            |] |> Array.map Async.AwaitTask |> Async.Parallel |> Async.Ignore
    }
    async { 
        for action in List.replicate 8 shake do 
            do! action 
    } |> Async.StartImmediate

The single shake action is bound to shake, and then the method returns an async block. In the block, the shake binding can then be replicated 8 times, and thanks to F#’s awesome for..in..do syntax, we have a loop but without a counter variable. We’re also still inside the async block so do! waits for the animation to completed on each run through the list of animations.

Refactoring

This working version has some repeated code that we could clean up a little bit. We can take that out and put that into a function:

1: 
let awaitParallel = Array.map Async.AwaitTask >> Async.Parallel >> Async.Ignore

If that looks confusing, check out my post on point free notation and also this post on function composition. With the helper function the rest of code now looks as follows:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
let shakeLandscapeAsync () = 
    let shake =  async {
        do! [|
                this.ScaleTo(1.1, 20u, Easing.Linear)
                this.TranslateTo(-30., 0., 20u, Easing.Linear)
            |] |> awaitParallel
        do! [| 
                this.TranslateTo(0., 0., 20u, Easing.Linear) 
            |] |> awaitParallel
        do! [| 
                this.TranslateTo(0., -30., 20u, Easing.Linear) 
            |] |> awaitParallel
        do! [|
                this.ScaleTo(1.0, 20u, Easing.Linear) 
                this.TranslateTo(0., 0., 20u, Easing.Linear)
            |] |> awaitParallel
    }
    async { 
        for action in List.replicate 8 shake do 
            do! action 
    } |> Async.StartImmediate

This is the final version that I left in my code base. I will mention one last step that could be done. Running animations sequentially like this might be rather common in a mobile app. So the async loop could be refactored out. An easy way to do this would be as an extension method on Async. Here’s an example:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
// In AsyncExtensions.fs
module Async

let RunSequentially asyncs = 
    async {
        for a in asyncs do 
            do! a
    } |> Async.StartImmediate   

Simple enough, it’s just pulled out the loop logic, (similar to how map has factored our the logic of a loop). The caller would then be as follows:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
let shakeLandscapeAsync () = 
    async {
        do! [|
                this.ScaleTo(1.1, 20u, Easing.Linear)
                this.TranslateTo(-30., 0., 20u, Easing.Linear)
            |] |> awaitParallel
        do! [| 
                this.TranslateTo(0., 0., 20u, Easing.Linear) 
            |] |> awaitParallel
        do! [| 
                this.TranslateTo(0., -30., 20u, Easing.Linear) 
            |] |> awaitParallel
        do! [|
                this.ScaleTo(1.0, 20u, Easing.Linear) 
                this.TranslateTo(0., 0., 20u, Easing.Linear)
            |] |> awaitParallel
    } |> List.replicate 8 |> RunSequentially

Because we are using the shake directly, we no longer need to bind it to shake. The piping operator is once again our friend and the code is easy to follow.

Summary

F#’s Async has all the power you need. let your codebase run wild with animations. Just remember that static methods on a C# Task are started immediately so they will need to be coupled with appropriate ! op (ie do!, let! or use!).

This post is licensed under CC BY 4.0 by the author.