How C# beats Scala in async programming

Written by anicolaspp | Published 2015/11/08
Tech Story Tags: asynchronous | scala | csharp

TLDRvia the TL;DR App

In my previous post, we explained how some features of the Scala programming languages overlap with the ones we can find in the C# programming language. But implicits and type extensions are not the only features both languages share.

The Actor Model

As Wikipedia presents

The actor model in computer science is a mathematical model of concurrent computation that treats “actors” as the universal primitives of concurrent computation: in response to a message that it receives, an actor can make local decisions, create more actors, send more messages, and determine how to respond to the next message received. It has been used both as a framework for a theoretical understanding of computation and as the theoretical basis for several practical implementations of concurrent systems.

Actors are a huge deal on the Scala world and C# really lacks of this pattern by default, but there is Akka.NET to save the day. Because Actors is just a pattern that can be implemented in any language we are going to skip it and jump directly to the core of multithreading in each of the languages.

Futures vs Tasks

Scala presents the idea of futures which work as a placeholder for a value that will be available at some point in the future. Futures are normally defined as generic place holders to hold these values. Let’s see how we can use them.

val aFuture = Future {1 to 1000000}

Here, aFuture holds the value of the sequence once it becomes available. It is also strongly typed so we can define it with a different syntax just to show its definition. It looks like this

val aFuture: Future[Seq[Int]] = Future {1 to 1000000}

Futures do not block the execution on the current thread, so it will be executed asynchronous in a different thread.

Let’s suppose we have a long task to execute async, for example, downloading a file from the web, we can use Futures to asynchronously do this operation. Here is how it goes

val aFuture = Future {val file = downloadFileFromTheWeb()file}

In this case, we just start downloading the file and in the meantime, we can still do some other interesting operations after the Future definition. But what happens at the point we need the result of the async operation? Futures present a mechanism to do this that in my opinion is something from the past, but let’s review it.

case class File(content: String)

object File{def apply(content: String) = new File(content)}

val aFuture = Future {val file = downloadFileFromTheWeb()file}

aFuture onSuccess {case File(content) => println(content)}

aFuture onFailure {case error => println("Error Downloading from the web: " + error.printStackTrace())}

The onSuccess and onFailure functions are defined so they will be executed based on the result of the long time computation and then we can act accordingly. However, if we need to wait for the result of the long time operation things get even more weird.

val aFuture = Future {val file = downloadFileFromTheWeb()file}

val file = Await.result(aFuture, 0 nanos)

Here we just wait for the computation to finish, however, the Future object has not control over this call since it is called on the execution context and not in the operation itself.

C# offers a better API to solve these problems even though it models these ideas in the same way Scala does. However, the .NET API is easier to use.

The same problem would look like this

public class File{public string Content { get; set; }}

var task = Task<File>.Factory.StartNew(() =>{var file = downloadFileFromTheWeb();

                return file;  
            });

Now if we want to wait for the result of this task we only say

var file = task.Result;Console.WriteLine(file.Content)

This will block until the task ends in the same way Await.result works. However, the control action is executed by the Task itself and not by a global executor context that needs to be imported into our execution environment.

C# also has better support for callbacks. They are not called callbacks anymore but task continuation. Let’s implement the same we did in Scala using C# continuations.

var taks = Task<File>.Factory.StartNew(() =>{var file = downloadFileFromTheWeb();

                return file;  
            })  
            .ContinueWith(t =>  
                {  
                    Console.WriteLine(t.Result);  
                }, TaskContinuationOptions.OnlyOnRanToCompletion);

This would be the same as onSuccess for Futures, however, ContinueWith method returns a Task, too, so it could be chained to other operations. TaskContinuationOptions decides in what case we execute the continuation so there is not need for different callbacks.

As we can see, C# Tasks are a more intuitive way to execute long time running tasks with a better support to solve what will come after the task has been completed. Still, there is another problem related to chaining task.

C# awaitable model

The async/await model was introduced to solve the problem of continuations and I truly believe that C# is far more advanced than other programming languages in this area.

Let’s review the following pseudo code:

var taks = Task<File>.Factory.StartNew(() =>{var file = downloadFileFromTheWeb();// <-- here want to do some other async operation such as writing the file content to a database// <-- then here we want to log to a file the db resultreturn file;})

We can achieve this by doing

var taks = Task<File>.Factory.StartNew(() =>{var file = downloadFileFromTheWeb();

   return file;  
})  
 .ContinueWith(t =>  
 {  
        Task<string>.Factory.StartNew(() =>  
        {  
            string fileContent = t.Result.Content;  
             return saveToDb(fileContent);  
          }).ContinueWith(x =>  
            {  
                logToFile(x.Result);  
             }) ;  
  }, TaskContinuationOptions.OnlyOnRanToCompletion);

Note how complicated the chaining of continuations can gets, and the same happens in Scala, but there is a feature that C# has that kills Scala in this aspect. The last code can be rewritten in the following way using the async/await pattern from C#

void main(...){var task = Do();

RefreshUIOrDoSomethingElse();  
  
//if we need to do something with the file we do  
File file = task.Result; // or File file = await task;  

}

private static async Task<File> Do(){var file = await Task<File>.Factory.StartNew(downloadFileFromTheWeb);

        var messageFromDb = await saveToDbAsync(file.Content);

        await logToFileAsync(messageFromDb);

        return file;  
    }

    static Task logToFileAsync(string s)  
    {  
        return Task.Factory.StartNew(()=>Console.WriteLine(s));     
    }

    private static List<string> db = new List<string>(); 

    static Task<string> saveToDbAsync(string fileContent)  
    {  
        return Task<string>.Factory.StartNew(() =>  
            {  
                db.Add(fileContent);  
                return fileContent;  
            });  
    }

Now, we are making use of the C# async/await API in order to get the code as clean as it is. Let’s analyze it just to make sure we all are in the same page.

First, we call the method Do() which basically returns a Task or Future in Scala, but the interesting part is what happens inside it. The most important part is that Do() does not block.

Second, inside Do() we are downloading the file async, but that is awaitable so at the moment we call

var file = await Task<File>.Factory.StartNew(downloadFileFromTheWeb);

the control flow returns to the main function. Once the file has been downloaded, the system selects an available thread and resume the Do() task from where it was. Yes! from where it was, so the next to execute in that task will be

var messageFromDb = await saveToDbAsync(file.Content);

but that sentence is also awaitable, so the control is returned once again to the main until the the DB has finished it’s work. When DB finishes, another thread is selected (doesn’t has to be the same thread as before) and the task resumes. The same happens to

await logToFileAsync(messageFromDb);

When it finished the task is resume again then the file is returned.

Note that await is strongly typed, so the result of its execution will be the generic type of the awaitable task. For example,

static Task<string> saveToDbAsync(string fileContent){...}

returns a Task<string> so if we await this task, the result will be a string

To be fair with Scala, something similar to async/await can be implemented using map. Let’s see an example.

aFuture onSuccess {case File(content) => println(content)}

The previous code can be implemented as follow

aFuture.map(println)

Let’s look at a more complicated example I found online

val firstLove = future {Thread.sleep(500)"i love you"}

val thenBetray = firstLove map {  
  case loveLetter => {  
    Console.println(loveLetter)  
    Thread.sleep(500)  
    "not really"  
  }  
}

thenBetray onSuccess {  
  case partingWords => Console.println(partingWords)  
}

We can see how map helps to chain the Future(s) but still the code can get quite messy. There are other Scala constructs that work along with map but they are not comparable to how C# solves this problem.

Endings

We saw how Scala implements long time running operation without blocking the calling thread. We also saw how the same can be implemented in C# by the use of class Task. We took a look at how continuations need to be implemented in Scala using callbacks and in C# by chaining Task(s). Ultimately we saw how C# solves the problem of chaining by the use of the async/await pattern and how natural it comes to developers.

I hope this comparison helps to synchronize developers in both worlds when talking about asynchronous programming. When someone talks about Futures you can map them to Tasks or the other way around.

Have fun, both languages are great, I enjoy them both.

Read next:

Implicit conversions in Scala for C# developers

Hacker Noon is how hackers start their afternoons. We’re a part of the @AMIfamily. We are now accepting submissions and happy to discuss advertising & sponsorship opportunities.

To learn more, read our about page, like/message us on Facebook, or simply, tweet/DM @HackerNoon.

If you enjoyed this story, we recommend reading our latest tech stories and trending tech stories. Until next time, don’t take the realities of the world for granted!


Published by HackerNoon on 2015/11/08