Viktor Klang
5 min readNov 9, 2015

--

Hi,

(Full disclosure: I am one of the main contributors to Akka and co-author of Scala Future)

thanks for writing an important blog post on async programming,
a style of programming that deserves more exposure than it currently gets.
I found a couple of minor factual errors in your blog post and I hope you don’t mind if I address some of them? Please do excuse me if I’m overstepping some boundary by doing so.

You wrote:

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.

There’s an underlying question that I think is worth answering: “Why does Await.result exist, and why doesn’t it exist *on* Future?”

When we designed Scala’s Future one thing we based it on was the experience with Akka Future (http://doc.akka.io/api/akka/1.3.1/?_ga=1.21043707.1579561034.1353497989#akka.dispatch.Future)

Note how it had `get` and `await` methods, which do similar things as the C# `Result` method — block the currently executing Thread from progressing until the value is available. Such a method is the counterpart to asynchronous, it is synchronizing the current Thread with the Thread which executes the Future — i.e. an API for synchronous programming.

Not only did we find that methods like that introduce performance problems due to blocking Threads (delay until the value is available but also due to Thread scheduler wakeup lag), but these methods also produce programs that are difficult to reason about since they can deadlock and become non-deterministic in their runtime behavior as things like GC-pauses etc may cause either spurious failures (where timeouts are supplied) or prolonged resource starvation due to unavailability of Threads to execute other logic.

Having a single method for performing these potentially dangerous operations and putting it outside of the Future API itself meant that it is easy to spot where blocking is performed as well as easy to outlaw it (by disallowing it to be present in source code). But we also took it a step further, by creating the BlockContext mechanism we also made it possible to the runtime to perform evasive manoeuvres in the presence of blocking. (http://www.scala-lang.org/api/current/index.html#scala.concurrent.BlockContext)

Note that your example is a prime example of my point: `Await.result(aFuture, 0 nanos)` has an inherent race-condition with the code that creates the value of `aFuture` as the logic may most definitely not have finished executing when the 0 nanosecond timeout is evaluated, leading to code that sometimes throw a TimeoutException and sometimes not.

You write:

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.

According to MSDN (https://msdn.microsoft.com/en-us/library/dd321468(v=vs.110).aspx) task.Result:

“Accessing the property’s get accessor blocks the calling thread until the asynchronous operation is complete; it is equivalent to calling the Wait method.”

What’s interesting about this comment is that it proves my point: We made it less-easy to do the wrong thing, and C#/.NET makes it easy to do the wrong thing — that’s not good!

You write:
However, the control action is executed by the Task itself and not by a global executor context that need to be imported into our execution environment.

I’m not sure what the above means: are you saying that the .NET version isn’t asynchronous (in which case we’re comparing apples to oranges) or you’re saying that it is executed on some internal to the runtime thread pool? In either case:
Await.result does not need an ExecutionContext. And the Scala version most definitely does not require to import a global ExecutionContext, it is typically recommended to, as the message says when there is none found in scope, to either import the global ExecutionContext, or require an ExecutionContext as an implicit parameter.

The underlying question being: “Why does Scala require an implicit ExecutionContext to execute code asynchronously?”

The answer is that if all you have is a single, shared, global thread pool then it becomes hard to shield parts of the system/application from other parts. For instance, code doing IO may want to use a specific kind of thread pool while code which does compute-heavy operations may want another. By specifying the ExecutionContext to be used there is full control over what gets executed where — leading to more predictable behavior during runtime, and with much more opportunities for tuning performance.

You write:

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.

Note that `onSuccess` and `onFailure` are deprecated in Scala 2.12 and there is a single method which has existed since day one called `onComplete` which allows you to deal with both success and failure cases.

You write:

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.

I’m not saying that you’re wrong, but I’m not sure that at this point anything w.r.t. intuitiveness has been proven. I’d love to see more explanations and examples about intuitiveness here!

You write:

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.

Here I’d like to have seen an equivalent Scala example to prove your point, so I’ll add it for posterity:

val file: Future[File] = Future(downloadFileFromTheWeb) flatMap {

file => Future(saveToDb(file.Content)).map(logToFile).map(_ => file)
}

You write:

There are other Scala constructs that work along with map but they are not comparable to how C# solves this problem.

This is a bit of a sweeping comment, the reader gets no chance of making any evaluation if that statement is true or not.

For instance, since Scala Futures have a monad, you can take advantage of for-comprehensions, which would make the same code above look like this:

val file = for {

file <- Future(downloadfileFromTheWeb)

messageFromDb <- safeToDb(file.Content)

_ <- logToFile(messageFromDb)
} yield file

Also, as Denys mentioned earlier, Scala does have support for async/await on Scala Futures, but the benefit is that it is implemented as a library and doesn’t involve having to change the language specification as had to happen with C#.

You write:

We took a look at how continuations need to be implemented in Scala using callbacks and in C# by chaining Task(s).

I hope I have demonstrated in this comment that continuations need not at all be implemented using callbacks, and that they are actively discouraged to be used given that 2 of 3 of them have been deprecated in Scala 2.12.

Massive thanks for taking the time to read this massive comment, I hope some of the information contained within was interesting!

I’m looking forward to reading the next blog post,

--

--

Viktor Klang

Deputy CTO at Typesafe Inc. Life-long learner, programmer, father of twins, flyfisher and Islay single malt fan. My tweets are my own. Website: viktorklang.com