Asynchrony in C# 5, Part One
blogs.msdn.com
blogs.msdn.com
In particular, I was kind of underwhelmed by the Task stuff they added in 4.0, because what I was expecting was something like this new await/async syntax - so now, seeing that they used the (comparatively primitive) Task infrastructure in order to build await/async, I feel bad for doubting them. So far on a cursory examination, these new features solve almost all my concurrency problems (at least, from the perspective of a compiler), and should let me throw out a ton of existing code that I've been maintaining. I basically wrote my own cooperative scheduler and infrastructure to do exactly this on top of the iterator features from C#2/3, and it's been a godsend, so it's going to be nice to have it in the compiler :)
In particular I'm pleased that instead of dictating that everyone use only cooperative threading or preemptive threading for concurrency, they're exposing both scheduling techniques with easy-to-use APIs that integrate well with each other. To me, this seems like a much better approach than picking a One True Concurrency Mechanism (like, for example, node did).
(ref: http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closi... )
I agree that in the example provided it does seem counter-intuitive at first, but v is clearly a variable tied to the the loop and there is nothing that explicitly tells us if it is scoped outside the loop or to the code-block within the loop.
If anything, I'd say it's the implementation of the iterator pattern which is broken, not the closures themselves (which I've had zero issues with).
That's their purpose, with the most common use-case being to postpone a piece of computation for later (until you've got everything you need).
So it cannot close over values, and if it did it would mean that you couldn't modify the values from the outer-scope (which you just closed over) ... making closures totally useless.
This isn't as straightforward in C#, as there are no immutable local bindings. It's annoying, but given the overall language, is probably the best fit.
You can compose and BeginOp EndOp functions into a function that returns an Async<T>
I've probably screwed up the exact function name but here's the psuedo code.
Example: Imagine a C# class as follows
public class Foo {
public AsnycResult BeginGetBar(int bar_id) { // something }
public Bar EndGetBar(AsnycResult result) {
// something
return bar;
}
}
Now to consume in F# create an extension method type Foo with
member x.AsyncGetBar = Async.FromAsyncResult(x.BeginGetBar, x.EndGetBar)
Now to use it let somefunc = async {
let x = new Foo();
let! bar = x.AsyncGetBar(10);
// do something with bar
}
What F# does is create a call back that is the rest of the function after the let!/do! statement, basically your code is split into a call back everytime you use a let!/do! binding.For a more interesting example lets say you want an async file copy.
let copy s1:Stream s2:Stream =
let buffer:byte[] = Array.init 8192
let rec copy = async {
let! read = s1.AsyncRead(0,buffer.Length,buffer)
match read with
| 0 -> ()
| x -> do! s2.AsyncWrite
return! copy()
}
The nice part about this is that copy is a fully reusable piece of code async code. So anytime you're in an async block and need to copy a file, so... let move s1 s2 = async {
do! copy s1 s2
do! delete s1
}Also, the pattern used by F# to do this sort of thing is exposed to the user so you can create any kind of monad instead of just one for async. In F# this behavior is integral to the language and not just syntactic sugar for a couple 'special' cases. So instead of learning one syntax for continuations and another for async, sql, etc, etc. You just have one system to learn that can be applied to many different problems, and you don't have to wait for Anders to bless your problem as important enough to have syntactic sugar added.
The other thing to note is that there will be many 'special cases' about using this code that don't work with typical C# code because of its mutability. It's similar to all the weirdness around using the yield statement. Like how you can't have an out parameter on a method that uses a yield statement and some weirdness around exceptions.
Personally I don't like magic keywords baked in the compiler.
Having a compiler do work for you and simplify your code is nice and you wont see me complain about this feature in particular.
On the other hand, I've made enough monadic extension classes (NullSafety, Object-transactions, etc) and extension methods to cover up for short-comings as I've seen them in my projects, and it always bugs me how this effectively forces me to clutter up my syntax.
Consider the following code
var x = GetSomeObject() // can be null
.SubProperty // can also be null
.Indexer[index]; // can also be null
This code can cause NullReferenceExceptions all over, so to do it safely, you need to add a bunch of if (object == null) checks.With my NullSafe-extensions, the expression looks like this:
var x = GetSomeObject().AsNullSafe()
.Do((x) => x.SubProperty)
.Do((x) => x.Indexer[index])
.Value; // can be null or default
Within the C# type-system, there is no way to get around this clutter and massive use of lambdas. Granted, it looks better than the corresponding code with manual null-checks for every step, but with lambda expression comes the cost of not being able to debug & correct code at runtime.The other side of the coin is that many larger businesses use coding standards to restrict the idioms in use across the enterprise. When the language is flexible enough to freely create new idioms (like Lisp and Smalltalk in extremis - the notion of DSLs came from these for good reasons), many folks fear that every codebase in the company will become its own silo of idioms, increasing the ramp-up time of new developers coming in. When an idiom comes packaged up from the tool vendor, "in the box" as it were, however, they can (hopefully) rely on new devs coming in to be up to speed already; as well as fairly confident it's been thought through properly.
Furthermore, third-party libraries and software component vendors in general benefit by being able to assume that everyone knows and can support the idioms.
But it's nice that async code in C# won't be as painful as it is today.
I would love to shadow that compiler team for a week or two, and get to sit in on some of those discussions.