The Evolution of Async-Programming on .NET Platform (TUP, Full)

Post on 10-May-2015

3.777 views 3 download

Tags:

Transcript of The Evolution of Async-Programming on .NET Platform (TUP, Full)

The Evolution of Async Programming on .NET Platform

ZhaoJie @ SNDANov, 2010

About Me

• 赵劼 / 老赵 / Jeffrey Zhao / 赵姐夫

• 日写代码三百行,不辞长做程序员• Blog: http://blog.zhaojie.me/

• Twitter: @jeffz_cn

• F#, Scala, JavaScript, Python, .NET, mono...

• Java (as the language) hater

Agenda

• Why & How

• .NET 1.0

• .NET 2.0 / C# 2.0

• .NET 3.0 / F#

• .NET 4.0 / Reactive Framework

• Future / C# vNext

Why?

Because the essence ofCloud, Web, Mobile

is asynchronous computations

How?

By providing powerful language features / programming model /

libraries

.NET 1.0

Two Raw Async Models

• Begin/End

• Event-based

• Both are callback-based• Which is just “asynchronous” means

Begin/End Async Modeldelegate AsyncCallback(IAsyncResult);

interface IAsyncResult { object AsyncState { get; } WaitHandle AsyncWaitHandle { get; } bool CompletedSynchronously { get; } bool IsCompleted { get; }}

void BeginXxx(arg1, arg2, ..., AsyncCallback, state);

TResult EndXxx(IAsyncResult);

Let’s write a “Transfer” method in 4 different

ways

Syncpublic static void Transfer(string url, Stream streamOut) { var request = WebRequest.Create(url); using (var response = request.GetResponse()) { var streamIn = response.GetResponseStream();

var size = 1024; var buffer = new byte[size]; while (true) { var lengthRead = streamIn.Read(buffer, 0, size); if (lengthRead <= 0) break; streamOut.Write(buffer, 0, lengthRead); } }}

AsyncIAsyncResult BeginTransfer(url, streamOut, ..., callback) { // keep context and BeginGetResponse}

void EndGetResponse(...) { // get streamIn and BeginRead}

void EndRead(...) { // data read, completed or BeginWrite}

void EndWrite(...) { // data wrote, BeginRead}

AsyncIAsyncResult BeginTransfer(url, streamOut, ..., callback) { // keep context and BeginGetResponse}

void EndGetResponse(...) { // get streamIn and BeginRead}

void EndRead(...) { // data read, completed or BeginWrite}

void EndWrite(...) { // data wrote, BeginRead}

1

AsyncIAsyncResult BeginTransfer(url, streamOut, ..., callback) { // keep context and BeginGetResponse}

void EndGetResponse(...) { // get streamIn and BeginRead}

void EndRead(...) { // data read, completed or BeginWrite}

void EndWrite(...) { // data wrote, BeginRead}

1

2

AsyncIAsyncResult BeginTransfer(url, streamOut, ..., callback) { // keep context and BeginGetResponse}

void EndGetResponse(...) { // get streamIn and BeginRead}

void EndRead(...) { // data read, completed or BeginWrite}

void EndWrite(...) { // data wrote, BeginRead}

1

2

3

AsyncIAsyncResult BeginTransfer(url, streamOut, ..., callback) { // keep context and BeginGetResponse}

void EndGetResponse(...) { // get streamIn and BeginRead}

void EndRead(...) { // data read, completed or BeginWrite}

void EndWrite(...) { // data wrote, BeginRead}

1

2

4

3

AsyncIAsyncResult BeginTransfer(url, streamOut, ..., callback) { // keep context and BeginGetResponse}

void EndGetResponse(...) { // get streamIn and BeginRead}

void EndRead(...) { // data read, completed or BeginWrite}

void EndWrite(...) { // data wrote, BeginRead}

1

2

4

3

5

AsyncIAsyncResult BeginTransfer(url, streamOut, ..., callback) { // keep context and BeginGetResponse}

void EndGetResponse(...) { // get streamIn and BeginRead}

void EndRead(...) { // data read, completed or BeginWrite}

void EndWrite(...) { // data wrote, BeginRead}

1

2

4

3

5

6

Robust & Syncpublic void Transfer(string url, Stream streamOut) { try { var request = WebRequest.Create(url); using (var response = request.GetResponse()) { var streamIn = response.GetResponseStream();

var size = 1024; var buffer = new byte[size]; while (true) { var lengthRead = streamIn.Read(buffer, 0, size); if (lengthRead <= 0) break; streamIn.Write(buffer, 0, lengthRead); } } } catch { // ... }}

Robust & AsyncIAsyncResult BeginTransfer(url, streamOut, ..., callback) { try { ... } catch { ... }}

void EndGetResponse(...) { try { ... } catch { ... }}

void EndRead(...) { try { ... } catch { ... }}

void EndWrite(...) { try { ... } catch { ... }}

Event-based Async Model

class XxxCompletedEventArgs : EventArgs { Exception Error { get; } TResult Result { get; }}

class Worker { event EventHandler<XxxCompletedArgs> XxxCompleted; void XxxAsync(arg1, arg2, ...);}

Code Locality is Broken

• Used to expressing algorithms linearly

• Async requires logical division of algorithms• No if / using / while / for ...

• Very difficult to• Combine multiple asynchronous operations

• Deal with exceptions and cancellation

.NET 2.0 / C# 2.0

“yield” for Iterators

IEnumerable<int> Numbers() { yield return 0; yield return 1; yield return 2;

}

“yield” for Iterators

IEnumerable<int> Numbers() { yield return 0; yield return 1; yield return 2;

}

MoveNext()

“yield” for Iterators

IEnumerable<int> Numbers() { yield return 0; yield return 1; yield return 2;

}

MoveNext()

MoveNext()

“yield” for Iterators

IEnumerable<int> Numbers() { yield return 0; yield return 1; yield return 2;

}

MoveNext()

MoveNext()

MoveNext()

“yield” for Iterators

IEnumerable<int> Numbers() { yield return 0; yield return 1; yield return 2;

}

MoveNext()

MoveNext()

MoveNext()

MoveNext()

Async with “yield”IEnumerator<int> Transfer(AsyncEnumerator ae, string url, Stream streamOut) { var request = WebRequest.Create(url); request.BeginGetResponse(ae.End(), null); yield return 1;

using (var response = request.EndGetResponse(ae.DequeueAsyncResult())) { var streamIn = response.GetResponseStream(); byte[] buffer = new byte[1024]; while (true) { streamIn.BeginRead(buffer, 0, buffer.Length, ae.End(), null); yield return 1;

int lengthRead = streamIn.EndRead(ae.DequeueAsyncResult()); if (lengthRead <= 0) break; streamOut.BeginWrite(buffer, 0, lengthRead, ae.End(), null); yield return 1;

streamOut.EndWrite(ae.DequeueAsyncResult()); } }}

Async with “yield”IEnumerator<int> Transfer(AsyncEnumerator ae, string url, Stream streamOut) { var request = WebRequest.Create(url); request.BeginGetResponse(ae.End(), null); yield return 1;

using (var response = request.EndGetResponse(ae.DequeueAsyncResult())) { var streamIn = response.GetResponseStream(); byte[] buffer = new byte[1024]; while (true) { streamIn.BeginRead(buffer, 0, buffer.Length, ae.End(), null); yield return 1;

int lengthRead = streamIn.EndRead(ae.DequeueAsyncResult()); if (lengthRead <= 0) break; streamOut.BeginWrite(buffer, 0, lengthRead, ae.End(), null); yield return 1;

streamOut.EndWrite(ae.DequeueAsyncResult()); } }}

“yield” after firing async operations

Async with “yield”IEnumerator<int> Transfer(AsyncEnumerator ae, string url, Stream streamOut) { var request = WebRequest.Create(url); request.BeginGetResponse(ae.End(), null); yield return 1;

using (var response = request.EndGetResponse(ae.DequeueAsyncResult())) { var streamIn = response.GetResponseStream(); byte[] buffer = new byte[1024]; while (true) { streamIn.BeginRead(buffer, 0, buffer.Length, ae.End(), null); yield return 1;

int lengthRead = streamIn.EndRead(ae.DequeueAsyncResult()); if (lengthRead <= 0) break; streamOut.BeginWrite(buffer, 0, lengthRead, ae.End(), null); yield return 1;

streamOut.EndWrite(ae.DequeueAsyncResult()); } }}

“yield” after firing async operations

continue when async operations

completed

“yield return” for Async

• Coming with new programming patterns

• Keep code locality• Good parts: support if / using / while / for ...

• But not perfect: cannot use try...catch

• The primitives for Fibers - lightweight computation units

.NET 3.0 / F#

F#

• Language by Don Syme, MS Research

• Strongly statically typed language

• Functional language with OO ability

• For industry and education• Open source (Apache 2.0)

• Cross-platform supported by Microsoft

Concurrency Challenges

• Shared State - Immutability

• Code Locality - async { ... }

• I/O Parallelism - async { ... }

• Scaling to Multi-Machine - Agents with async { ... }

What’s async { ... }

... the principle we go by is, don't expect to see a particular concurrency model put into C# because there're many different concurrency model ... it's more about finding things are common to to all kinds of concurrency ...

- Anders Hejlsberg

Async Workflow

async {   let! res = <async work> ...}

Async Workflow

async {   let! res = <async work> ...}

React!

Async Workflow

async {   let! res = <async work> ...}

React!

an HTTP Responsean UI Event

a Timer Callbacka Query Response

a Web Servcie Responsea Disk I/O Completion

an Agent Message

Async in F#let transferAsync (url: string) (streamOut: Stream) = async {

let request = WebRequest.Create(url) use! response = request.AsyncGetResponse() let streamIn = response.GetResponseStream() let rec transferDataAsync (buffer: byte[]) = async { let! lengthRead = streamIn.AsyncRead(buffer, 0, buffer.Length) if lengthRead > 0 then do! streamOut.AsyncWrite(buffer, 0, lengthRead) do! transferDataAsync buffer } do! transferDataAsync (Array.zeroCreate 1024)}

Async in F#let transferAsync (url: string) (streamOut: Stream) = async {

let request = WebRequest.Create(url) use! response = request.AsyncGetResponse() let streamIn = response.GetResponseStream() let rec transferDataAsync (buffer: byte[]) = async { let! lengthRead = streamIn.AsyncRead(buffer, 0, buffer.Length) if lengthRead > 0 then do! streamOut.AsyncWrite(buffer, 0, lengthRead) do! transferDataAsync buffer } do! transferDataAsync (Array.zeroCreate 1024)}

simply generate from Begin/End methods

AsyncGetResponse

AsyncRead

AsyncWrite

How async { ... } Worksasync { let! img = AsyncRead "http://..." printfn "loaded!" do! AsyncWrite img @"c:\..." printfn "saved!" }

How async { ... } Worksasync { let! img = AsyncRead "http://..." printfn "loaded!" do! AsyncWrite img @"c:\..." printfn "saved!" }

async.Delay(fun -> async.Bind(AsyncRead "http://...", (fun img -> printfn "loaded!" async.Bind(AsyncWrite img @"c:\...", (fun () -> printfn "saved!" async.Return())))))

=

F# Async Workflow• Library, not a language feature• Based on Computation Expressions in F#

• Support all kinds of language constructions• Error handling: try...catch

• Loop: while / for (like “foreach” in C#)

• Others: if / use (like “using” in C#), etc.

• Easy to• Combine multiple asynchronous operations

• Deal with exceptions and cancellation

F# Resourceshttp://fsharp.net

Programming F# Expert F# 2.0 Real World FP

.NET 4.0 / Reactive Framework

Reactive Framework

Fundamentally change the way you think about coordinating and

orchestrating asynchronous and event-based programming

How

By showing that asynchronous and event-base computations are just

push-based collections

Environment

Program

Interactive Reactive

Enumerable Collections

interface IEnumerable<out T> { IEnumerator<T> GetEnumerator();}

interface IEnumerator<out T> { bool MoveNext(); T Current { get; }}

Dualize

Observable Collections

interface IObservable<out T> { IDisposable Subscribe(IObserver<T> o)}

interface IObserver<in T> { void OnCompleted(); void OnNext(T item); void OnError(Exception ex);}

IEnumerable & IEnumerator are prototypical interfaces for interactive collections and interactive programs.

IObservable & IObserver are prototypical interfaces for observable collections and reactive, asynchronous & event-based programs.

LINQ to Observable

If you are writingLINQ or declarative codein an interactive program...

LINQ to Observable

If you are writingLINQ or declarative codein an interactive program...

You already know how to use it!

Again

... the principle we go by is, don't expect to see a particular concurrency model put into C# because there're many different concurrency model ... it's more about finding things are common to to all kinds of concurrency ...

- Anders Hejlsberg

Again

... the principle we go by is, don't expect to see a particular concurrency model put into C# because there're many different concurrency model ... it's more about finding things are common to to all kinds of concurrency ...

- Anders Hejlsberg

Move by WASD// filter the KeyPress events when playingvar keyPress = GetKeyPress().Where(_ => isPlaying);

// filter the events to move leftvar moveLeft = from ev in keyPress where ev.EventArgs.KeyChar == 'a' where ball.Left > 0 select ev;moveLeft.Subscribe(_ => ball.Left -= 5);

// filter the events to move topvar moveTop = from ev in keyPress where ev.EventArgs.KeyChar == 'w' where ball.Top > 0 select ev;moveTop.Subscribe(_ => ball.Top -= 5);

Mouse Drag and Drawvar mouseMove = GetMouseMove();var mouseDiff = mouseMove.Zip(mouseMove.Skip(1), (prev, curr) => new { PrevPos = new Point( prev.EventArgs.X, prev.EventArgs.Y), CurrPos = new Point( curr.EventArgs.X, curr.EventArgs.Y) });

var mouseDrag = from _ in GetMouseDown() from diff in mouseDiff.TakeUntil(GetMouseUp()) select diff;

mouseDrag.Subscribe(d => DrawLine(d.PrevPos, d.CurrPos));

Everything in Web is Asynchronous

• User actions

• AJAX requests

• Animations

• ...

Rx in JavaScript

• A full featured port for JavaScript• Easy-to-use conversions from existing DOM,

XmlHttpRequest, etc

• In a download size of less than 7kb (gzipped)

• Bindings for various libraries / frameworks• jQuery

• MooTools

• Dojo

• ...

Drag and Draw in JSvar target = $("#paintPad");var mouseMove = target.toObservable("mousemove");var mouseDiff = mouseMove.Zip(mouseMove.Skip(1), function(prev, curr) { return { prevPos: { x: prev.clientX, y: prev.clientY }, currPos: { x: curr.clientX, y: curr.clientY } }; });

var mouseDown = target.toObservable("mousedown");var mouseUp = target.toObservable("mouseup");var mouseDrag = mouseDown.SelectMany(function() { mouseDiff.TakeUntil(mouseUp);});

mouseDrag.Subscribe( function(d) { drawLine(d.prevPos, d.currPos); });

Wikipedia Lookupvar textBox = $("#searchInput");var searcher = textBox .toObservable("keyup") .Throttle(500) .Select(function(_) { return textBox.val(); }) .Select(function(term) { return queryWikipedia(term); }) .Switch();

searcher.Subscribe( function(data) { var results = $("#results"); results.empty(); $.each(data, function(_, value) { results.append($("<li/>").text(value)); }); }, function(error) { $("#error").html(error); });

Time Flies like an Arrowvar container = $("#container");var mouseMove = container.toObservable("mousemove");

for (var i = 0; i < text.length; i++) { (function(i) { var ele = $("<span/>").text(text.charAt(i)); ele.css({position: "absolute"}).appendTo(container);

mouseMove.Delay(i * 100).Subscribe(function (ev) { ele.css({ left: ev.clientX + i * 20 + 15 + "px", top: ev.clientY + "px" }); });

})(i);

Benefits of Rx

• Easy to composite and coordinate async operations

• Express the algorithm in functional ways• Helper method: For / While / If / Try / Switch...

• Easy to be unit tested

• ...

Rx & Language Features

• Features in C# that Rx uses• Extension method

• Lambda expression & closure

• Type inference

• LINQ query expression

• Rx has been implemented in ...• C# & VB

• JavaScript

• F#

Portability

• Rx can be easily ported to various languages• Scala

• Ruby

• Python

• modern languages with basic functional features

• Almost impossible to implement Rx in Java• Cannot extend a type without breaking code

• Missing enough functional features

Rx Resources

• Matthew Podwysocki• http://codebetter.com/blogs/matthew.podwysocki/

• Reactive Framework on MSDN DevLabs• http://msdn.microsoft.com/en-us/devlabs/

ee794896.aspx

• Tomáš Petříček• http://tomasp.net/

Comparison

• F# Async Workflow• Elegant, simple, easy to use

• Can only be used at server-side (WebSharper come to rescure?)

• Reactive Framework• Can be used at both server-side and client-side.

• New async model brings learning cost.

Out of Topic

Can we programming like F# in JavaScript?

Jscex & Jscex.Async

• Computation Expressions and Async Workflow implemented in JavaScript

• http://github.com/jeffreyzhao/jscex

Clock

var drawClockAsync = eval(Jscex.compile("$async", function(interval) { while (true) { drawClock(new Date()); $await(Jscex.Async.sleep(interval)); } }));

Jscex.Async.start(drawClockAsync(1000));

Animationvar moveAsync = eval(Jscex.compile("$async", function(e, start, end, duration) { for (var t = 0; t < duration; t += 50) { e.style.left = start.x + (end.x - start.x) * t / duration; e.style.top = start.y + (end.y - start.y) * t / duration; $await(Jscex.Async.sleep(50)); }

e.style.left = end.x; e.style.top = end.y; }));

var moveSquareAsync = eval(Jscex.compile("$async", function(e) { $await(moveAsync(e, {x:100, y:100}, {x:400, y:100}, 1000)); $await(moveAsync(e, {x:400, y:100}, {x:400, y:400}, 1000)); $await(moveAsync(e, {x:400, y:400}, {x:100, y:400}, 1000)); $await(moveAsync(e, {x:100, y:400}, {x:100, y:100}, 1000));}));

C# vNext

async Task<XElement> GetRssAsync(string url) { var client = new WebClient(); var task = client.DownloadStringTaskAsync(url); var text = await task; var xml = XElement.Parse(text); return xml;}

Source

Task<XElement> GetRssAsync(string url) { var $builder = AsyncMethodBuilder<XElement>.Create(); var $state = 0; TaskAwaiter<string> $a1; Action $resume = delegate { try { if ($state == 1) goto L1; var client = new WebClient(); var task = client.DownloadStringTaskAsync(url); $state = 1; $a1 = task.GetAwaiter(); if ($a1.BeginAwait($resume)) return; L1: var text = $a1.EndAwait(); var xml = XElement.Parse(text); $builder.SetResult(xml); } catch (Exception $ex) { $builder.SetException($ex); } }; $resume(); return $builder.Task;}

Compiled

Conclusion

• Async Programming is difficult

• New programming language / feature / library / model can help

Q & A

Thanks