Показаны сообщения с ярлыком async. Показать все сообщения
Показаны сообщения с ярлыком async. Показать все сообщения

воскресенье, 18 апреля 2010 г.

MailboxProcessors: Practical Application

Last time we have examined the API of MailboxProcessors. Now it is time to see, how this concept can be applied to solve real-world problems.

Just as a quick reminder - "ping-pong" sample: two mailboxes, first one sends Ping request to second one, second replies with Pong. After specified number of iteratons execution stops.

type Message = Ping of AsyncReplyChannel<unit> | Stop

let pong (inbox : MailboxProcessor<Message>)=
let rec loop = async {
let! msg = inbox.Receive()
match msg with
| Ping(reply) ->
reply.Reply()
return! loop
| Stop -> ()
}
loop
let ping = MailboxProcessor.Start(fun (inbox : MailboxProcessor<int>) ->
let rec start () = async {
printfn "ping::waiting for number of iterations..."
let! n = inbox.Receive()
printfn "ping::%d received, start sending ping" n
do! loop n (MailboxProcessor.Start(pong))
}
and loop n (pong : MailboxProcessor<Message>) = async {
if n > 0 then
printfn "ping::sending ping, %d remaining" n
do! pong.PostAndAsyncReply(Ping)
printfn "ping::pong received"
do! loop (n - 1) pong
else
printfn "ping::exiting"
pong.Post(Stop)
}
start ()
)
ping.Post(5)
(*
ping::waiting for number of iterations...
ping::5 received, start sending ping
ping::sending ping, 5 remaining
ping::pong received
ping::sending ping, 4 remaining
ping::pong received
ping::sending ping, 3 remaining
ping::pong received
ping::sending ping, 2 remaining
ping::pong received
ping::sending ping, 1 remaining
ping::pong received
ping::exiting
*)

As opposed to trivial Pong function, Ping has a noteworthy moment, it defines a simple state machine with two states. First - obtaining for number of iterations, second - sending ping and waiting for reply.

Ping-pong sample fits nice for demo purposes but suffers from grave shortcoming - it is purely synthetic and useless in real life. It would be very nice to see the sample where MailboxProcessors can uncover their power and capabilities...

Web Sockets

The Web sockets specification - developed as part of the HTML5 initiative - introduced the Web Socket JavaScript interface, which defines a full-duplex single socket connection over which messages can be sent between client and server. The Web Socket standard attempts to simplify much of the complexity around bi-directional web communication and connection management.

The idea behind Web Sockets is very simple - client initiates TCP connection and performs handshake with server. After successful completion of handshake web sockets works pretty similar to usual sockets. Data is sent in the form of UTF-8 text. Frame can contain data where high bit is not set (0x00 to 0x7F) - this kind id data is transferred as a stream of bytes started with 0x00 and terminated with 0xFF. If high bit in data is set then this data should have leading length indicator which is encoded as a series of 7-bit bytes stored in octets with the 8th bit being set for all but the last byte.

For demonstration we will create simple web socket server that accepts requests from browser (Google Chrome already supports web sockets so it will be out test animal) and periodically sent JSON-encoded messages with current time information.

Server components.

Server
  • Request dispatcher accepts incoming connections and spawns worker to handle client requests
  • Worker sends and receives messages from particular client. Worker also incapluates all transport/serialization details so external components just post messages to worker.
  • Controller maintains list of workers and broadcasts messages.
  • Timer periodically send messages to Controller.
open System
open System.IO
open System.Net
open System.Net.Sockets
open System.Text
open System.Threading
open System.Runtime.Serialization

[<DataContract>]
type Time =
{ [<DataMember(Name = "hour")>] mutable Hour : int
[<DataMember(Name = "minute")>] mutable Minute : int
[<DataMember(Name = "second")>] mutable Second : int }
static member New(dt : DateTime) = {Hour = dt.Hour; Minute = dt.Minute; Second = dt.Second}

type Msg =
| Connect of MailboxProcessor<Time>
| Disconnect of MailboxProcessor<Time>
| Tick of Time

let startMailboxProcessor ct f = MailboxProcessor.Start(f, cancellationToken = ct)

let timer (ctrl : MailboxProcessor<Msg>) interval = async {
while true do
do! Async.Sleep interval
ctrl.Post(Tick <| Time.New(DateTime.Now))
}

let runController (ct : CancellationToken) = startMailboxProcessor ct (fun (inbox : MailboxProcessor<Msg>) ->
let listeners = new ResizeArray<_>()
async {
while not ct.IsCancellationRequested do
let! msg = inbox.Receive()
match msg with
| Connect l -> listeners.Add(l)
| Disconnect l -> listeners.Remove(l) |> ignore
| Tick msg -> listeners.ForEach(fun l -> l.Post msg)
}
)

let runWorker (tcp : TcpClient) (ctrl : MailboxProcessor<Msg>) ct = ignore <| startMailboxProcessor ct (fun (inbox : MailboxProcessor<Time>) ->
let rec handshake = async {
let ns = tcp.GetStream()
let reader = new StreamReader(ns)
let lines =
reader.ReadLine()
|> Seq.unfold(fun l -> if String.IsNullOrEmpty l then None else Some(l, reader.ReadLine()))
|> Seq.toArray

match lines with
| [| "GET /timer HTTP/1.1"; "Upgrade: WebSocket"; "Connection: Upgrade"; _; _|] ->
// TODO : parse WebSocket-Origin and WebSocket-Location

// send server handshake part
let serverHandshakePart =
"HTTP/1.1 101 Web Socket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
WebSocket-Origin: file://
WebSocket-Location: ws://localhost/timer

"B
do! ns.AsyncWrite(serverHandshakePart)
return! run ns
| _ ->
//validation failed - close connection
tcp.Close()
}
and run (ns : NetworkStream) = async {
let json = System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof<Time>)
ctrl.Post(Connect inbox)
try
while not ct.IsCancellationRequested do
let! time = inbox.Receive()

let ms = new MemoryStream()
json.WriteObject(ms, time)

do ns.WriteByte(byte 0x00)
do! ns.AsyncWrite(ms.ToArray())
do ns.WriteByte(byte 0xFF)
finally
ns.Close()
ctrl.Post(Disconnect inbox)
}
handshake
)

let runRequestDispatcher () =
let listener = new TcpListener(IPAddress.Loopback, 80)
let cts = new CancellationTokenSource()
let token = cts.Token

let controller = runController token
Async.Start (timer controller 1000, token)

let main = async {
try
listener.Start(10)
while not cts.IsCancellationRequested do
let! client = Async.FromBeginEnd(listener.BeginAcceptTcpClient, listener.EndAcceptTcpClient)
runWorker client controller token
finally
listener.Stop()
}

Async.Start(main, token)

{ new IDisposable with member x.Dispose() = cts.Cancel()}

let dispose = runRequestDispatcher ()
printfn "press any key to stop..."
Console.ReadKey() |> ignore
dispose.Dispose()

index.html

<html>
<head>
<script>
ws = new WebSocket("ws://localhost/timer");
ws.onopen = function() { alert("Opened"); };
ws.onclose = function() { alert("Closed"); };
ws.onmessage = function(evt) {
var date = eval('(' + evt.data + ')');
hour.innerText = date.hour;
minute.innerText = date.minute;
second.innerText = date.second;
};
</script>
<style type="text/css">
table, td, th
{
font-family:"Trebuchet MS", Arial, Helvetica, sans-serif;
border:1px solid #A7C942;
padding:3px 7px 2px 7px;
text-align:center;
}
td
{
font-weight:bold;
font-size:20px;
}
th
{
font-size:14px;
font-style:italic;
background-color:#A7C942;
color:white;
}
</style>
</head>
<body>
<table>
<tr>
<th>Hour</th>
<th>Minute</th>
<th>Second</th>
</tr>
<tr>
<td id="hour"></td>
<td id="minute"></td>
<td id="second"></td>
</tr>
</table>
</body>
</html>

I've opened 3 Chrome windows thus creating 3 workers. As we can notice that timer values in all browsers are changed simultaneously

p1

p2

 p3

пятница, 2 апреля 2010 г.

Evoking by "Patterns of Parallel Programming" 1.1

In previous post I've made the sketch of Async.Any method, however code in sample should not be treated as a real-world implementation but rather as a demonstration of approach. Now we will improve existing version so the interest to it can be switched from academical to practical. Many thanks to Dmitry Lomov, as he reveals number of very serious flaws.

Let's list all imperfections of current solution:

  1. User continuation was invoked under the lock as well as (possibly) registered cancellation handlers
  2. CancellationTokenSource is not disposed after finishing the computation
  3. Exceptions are not handled at all
  4. All computations are executed in new fresh thread instead of enqueueing job to ThreadPool
  5. Base computation can already have ambient cancellation token and for now this token is ignored.

And now without further ado I'm glad to present you new version:

open System.Threading

type Async with
static member Any(asyncs : seq<Async<'T>>) =
let arr = Seq.toArray asyncs

// signals cancel only once, subsequent calls are ignored
// returns true if cancel signal issued; otherwise - false
let cancel cancelled (cts : CancellationTokenSource) =
if Interlocked.CompareExchange(cancelled, 1, 0) = 0
then cts.Cancel(); true
else false

let createComputation cancelled (cts : CancellationTokenSource) = Async.FromContinuations(fun (cont, econt, ccont) ->

let remaining = ref (int64 arr.Length)
if !remaining = 0L then failwith "Sequence should contain at least one computation"

let cancelled = ref 0

// decrements number of active computations
// disposes cancellation token source when last computation ends
// returns true if Dispose is called; otherwise - false
let decrement () =
if Interlocked.Decrement(remaining) = 0L
then cts.Dispose(); true
else false

let exceptions = new ResizeArray<_>()

let saveSuccess v =
if cancel cancelled cts
then
decrement() |> ignore
cont v
else
decrement() |> ignore

// stores exception
// calls error econt if all subcomputations were completed with errors
let saveError ex =
lock exceptions (fun () -> exceptions.Add(ex))
if decrement() && (exceptions.Count = arr.Length) then
econt(new System.AggregateException(exceptions))

for c in arr do
Async.StartWithContinuations(
async {
do! Async.SwitchToThreadPool()
return! c
},
saveSuccess,
saveError,
(fun _ -> decrement() |> ignore),
cancellationToken = cts.Token
)
)
async {
let cancelled = ref 0
let cts = new CancellationTokenSource()
use! d = Async.OnCancel(fun() -> cancel cancelled cts |> ignore)
let! r = createComputation cancelled cts
return r
}

Notes:

  1. cancel function can be called multiple times. First time leads to triggering cancellation on specified CancellationTokenSource,subsequent calls are ignored. When success continuation (saveSuccess) is invoked for the first time - it signals cancellation and delegates actual continuation.
  2. decrement function counts down when every computation ends (independently from completion type). Finally it disposes associated CancellationTokenSource.
  3. Async.OnCancel propagates externally signalled cancellation to nested computations.

среда, 31 марта 2010 г.

Evoked by "Patterns of Parallel Programming" (part 1)

One section in excellent article "Patterns of Parallel Programming" is dedicated to idiom "Speculative processing". In brief we can start multiple computation in parallel (utilizing advantages of multiple cores), take first result and ignore others. Unfortunatly F# library doesn't provide builtin primitive for this strategy. Code below contains sketch of possible solution.

open System.Threading

type Async with
static member Any(asyncs : seq<Async<'T>>) =
let value = ref false
let cts = new CancellationTokenSource()
Async.FromContinuations(fun (cont, econt, ccont) ->
// accepts result value only once, subsequent calls will be ignored
let kont v =
do lock value (fun () ->
match !value with
| true -> ()
| false ->
value := true
cts.Cancel()
cont v
)
// runs specified computation in new thread
let wrapper a = async {
do! Async.SwitchToNewThread()
return! a
}
for a in asyncs do
Async.StartWithContinuations(wrapper a, kont, econt, ignore, cancellationToken = cts.Token)
)

We wrap specified asyncs so they will be started in threadpool threads. After first successful completion result is passed to awaiting continuation and cancellation is signalled. Subsequent results (if any occured) will be ignored.

Update: post with corrected version

среда, 24 февраля 2010 г.

Adapter for event-based pattern. With a blackjack and ...other components

Recently during overview of Async module I’ve promised to make an adapter for async workflows, so they can be consumed as implementations of a good old event-based async pattern. Let's start.

Define inheritor from AsyncCompletedEventArgs class (with a few helper functions – we will need them later). It will be used as a container for operation result.

type OperationCompletedEventArgs<'T>(result : 'T, ex, cancelled) = 
inherit AsyncCompletedEventArgs(ex, cancelled, null)
member this.Result = result

let raiseSuccess f (evt : Event<_, _>) r = evt.Trigger(null, f r null false)
let raiseError f (evt : Event<_, _>) ex = evt.Trigger(null, f (Unchecked.defaultof<_>) ex false)
let raiseCancel f (evt : Event<_, _>) _ = evt.Trigger(null, f (Unchecked.defaultof<_>) null true)

let createResult res ex cancelled = new OperationCompletedEventArgs<_>(res, ex, cancelled)
let createUnit () ex cancelled = new AsyncCompletedEventArgs(ex, cancelled, null)

Declare two wrapper interfaces: first interface will represent void operation, second - operation that can return a result.

type IOperation<'TArg> = 
[<CLIEvent>]
abstract Completed : IEvent<EventHandler<AsyncCompletedEventArgs>, _>
abstract RunAsync : 'TArg -> unit
abstract CancelAsync : unit -> unit

type IOperation<'TArg, 'TResult> =
[<CLIEvent>]
abstract Completed : IEvent<EventHandler<OperationCompletedEventArgs<'TResult>>, _>
abstract RunAsync : 'TArg -> unit
abstract CancelAsync : unit -> unit

These operations will behave as prescribed in article:Implementing the Event-based Asynchronous Pattern:

  • Completed event is raised when async execution is finished, cancelled or failed
  • CancelAsync method is used to signal a cancel for running operation
  • RunAsync method starts async operation.

Single argument ('TArg) in RunAsync method doesn't mean that we will be able to adapt only one-argument computations. Sometimes one generic argument is enough especially if type parameter can be substituted by tuple of any rank :).

Finally, implementation itself:

type Async with
static member AsEventBased(computation) =
let cts = new CancellationTokenSource()
let event = new Event<_, _>()
{ new IOperation<_, _> with
[<CLIEvent>]
member this.Completed = event.Publish
member this.RunAsync(arg) =
Async.StartWithContinuations(
computation arg,
raiseSuccess createResult event,
raiseError createResult event,
raiseCancel createResult event,
cts.Token)
member this.CancelAsync() = cts.Cancel() }

static member AsEventBased(computation) =
let cts = new CancellationTokenSource()
let event = new Event<_, _>()
{ new IOperation<_> with
[<CLIEvent>]
member this.Completed = event.Publish
member this.RunAsync(arg) =
Async.StartWithContinuations(
computation arg,
raiseSuccess createUnit event,
raiseError createUnit event,
raiseCancel createUnit event,
cts.Token)
member this.CancelAsync() = cts.Cancel() }

Well, adapter is ready but I need to ensure, that it actually works. Everybody are sick and tired of parallel download demonstration, so I've decided to make something new: async computation that makes requests to Google translate through AJAX API. So said, so done.

1. Google Translate returns data as JSON and BCL already includes DataContractJsonSerializer. Define data contracts:

open System.IO
open System.Web
open System.Net

open System.Runtime.Serialization
open System.Runtime.Serialization.Json

[<DataContract>]
type ResponseData = {
[<DataMember(Name = "translatedText")>]
mutable TranslatedText : string
[<DataMember(Name = "detectedSourceLanguage")>]
mutable DetectedSourceLanguage : string
}
[<DataContract>]
type Response = {
[<DataMember(Name = "responseData")>]
mutable ResponseData : ResponseData
[<DataMember(Name = "responseDetails")>]
mutable Details : string
[<DataMember(Name = "responseStatus")>]
mutable Status : int
}

2. Create a workflow:

let translate (referer, word : string, sourceLang, targetLangs) =
let translateOne lang = async {
let s = new DataContractJsonSerializer(typeof<Response>)

let requestString =
sprintf "http://ajax.googleapis.com/ajax/services/language/translate?v=1.0&q=%s&langpair=%s%%7C%s" (HttpUtility.UrlEncode word) sourceLang lang
let req = HttpWebRequest.Create(requestString) :?> HttpWebRequest
req.Referer <- referer
req.Timeout <- 4000
let! resp = req.AsyncGetResponse()
use stream = resp.GetResponseStream()
return s.ReadObject(stream) :?> Response
}
targetLangs
|> Seq.map translateOne
|> Async.Parallel

Remarks: Evidently sending request is trivial, arguments are encoded in url. If empty string is passed in first position of langpair argument then Translate service will try to autodetect source language. Also presense of correct Referer header is mandatory (prescribed by Google Terms of Service).

3. Declare a helper module with language-related settings

module Languages 

[<Literal>]
let Unknown = "UNKNOWN"

type Lang = {
Name : string
Id : string
}
with
member this.IsAutodetect = this.Id = ""

let Autodetect = { Name = "AUTODETECT"; Id = ""}

let All =
[
{Name="BELARUSIAN" ; Id = "be"}
{Name="BENGALI" ; Id = "bn"}
{Name="GUJARATI" ; Id = "gu"}
{Name="HEBREW" ; Id = "iw"}
{Name="HINDI" ; Id = "hi"}
{Name="HUNGARIAN" ; Id = "hu"}
{Name="ICELANDIC" ; Id = "is"}
{Name="INDONESIAN" ; Id = "id"}
{Name="INUKTITUT" ; Id = "iu"}
{Name="IRISH" ; Id = "ga"}
{Name="ITALIAN" ; Id = "it"}
{Name="JAPANESE" ; Id = "ja"}
{Name="KANNADA" ; Id = "kn"}
{Name="KAZAKH" ; Id = "kk"}
{Name="KHMER" ; Id = "km"}
{Name="PERSIAN" ; Id = "fa"}
{Name="POLISH" ; Id = "pl"}
{Name="PORTUGUESE" ; Id = "pt-PT"}
{Name="PUNJABI" ; Id = "pa"}
{Name="ROMANIAN" ; Id = "ro"}
{Name="RUSSIAN" ; Id = "ru"}
{Name="SANSKRIT" ; Id = "sa"}
]
let private lookup = All |> Seq.map(fun x -> (x.Id, x.Name)) |> dict
let Find n =
match lookup.TryGetValue n with
| true, v -> v
| false, _ -> Unknown

I've intentionally trimmed it because of size, full list of supported languages can be found here.

4. Create a client application(WPF based). I've cheated a bit and made F# client but pretended that I've never heart about Async module. All interaction with real workflow implementation will be performed through adapter. In fact I've cheated twice, instead of crafting UI object by object I've defined structure in XAML and load it with XamlReader.

type LanguageListItem = {
[<DefaultValue>]
mutable Checked : bool
Language : Languages.Lang
}

[<STAThread>]
do
let referer = "put_your_referer here"

let win = load "MainForm.xaml" :?> Window
let input = win.FindName("input") :?> TextBox
let translateBtn = win.FindName("translate") :?> Button
let langsList = win.FindName("languages") :?> ItemsControl
let sourceLangCombo = win.FindName("sourceLanguages") :?> ComboBox

let languages = Languages.All |> List.map (fun l -> {Language = l})
langsList.ItemsSource <- languages

sourceLangCombo.ItemsSource <- [Languages.Autodetect] @ Languages.All
sourceLangCombo.SelectedIndex <- 0

translateBtn.Click.Add(fun _ ->
let phrase = input.Text
if not <|String.IsNullOrWhiteSpace(phrase) then
let checkedLangs = languages |> List.choose(fun l -> if l.Checked then Some(l.Language) else None)
let sourceLang = sourceLangCombo.SelectedItem :?> Languages.Lang
let op = Async.AsEventBased(translate)

let rec disposable = op.Completed.Subscribe(handler)
and handler args =
disposable.Dispose()
if args.Error <> null then
MessageBox.Show(args.Error.Message) |> ignore
else if not <| args.Cancelled then
showResults phrase sourceLang win (args.Result |> Seq.zip checkedLangs |> List.ofSeq)

op.RunAsync(referer, phrase, sourceLang.Id, checkedLangs |> Seq.map(fun l -> l.Id))
)

let app = new Application()
app.Run(win) |> ignore

showResults function accepts source text, source language, and results and displays all information on new window (Owner = parent win). Its signature: let showResults text (sourceLang : Languages.Lang) parentWin (results : list). Implementation is similar to main function and thus omitted.

Xaml of main window (UI design is not strong point of mine :) )

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
WindowStyle="SingleBorderWindow"
WindowStartupLocation="CenterScreen"
Height="350" Width="450">
<Window.Resources>
<ResourceDictionary Source="/PresentationFramework.Luna, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, ProcessorArchitecture=MSIL;component/themes/luna.metallic.xaml" />
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition MaxHeight="25"/>
<RowDefinition />
<RowDefinition MaxHeight="25" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition MaxWidth="120"/>
</Grid.ColumnDefinitions>
<TextBox Height="23" x:Name="input" Grid.Row="0"/>
<ComboBox x:Name="sourceLanguages" Grid.Row="0" Grid.Column="1">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ScrollViewer Grid.Row="1" Grid.ColumnSpan="2">
<ItemsControl x:Name="languages" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderThickness="1" BorderBrush="Black">
<CheckBox Margin="5,2,5,2" Content="{Binding Path=Language.Name}" x:Name="check" IsChecked="{Binding Path=Checked}"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<Button Content="Translate" Grid.Row="2" Grid.ColumnSpan="2" x:Name="translate" Margin="0,2,0,2"/>
<Image Grid.Column="1" Grid.Row="1" Grid.RowSpan="2" Height="150" HorizontalAlignment="Left" Margin="252,244,0,0" Stretch="Fill" VerticalAlignment="Top" Width="200" />
</Grid>
</Window>

Main window


main_window

Results


results

суббота, 20 февраля 2010 г.

Overview of F# Async module

This post I’d like to dedicate to reviewing functionality of Async module – creating and manipulating async computations.

All functions can be divided into the following groups:

Auxiliary functions used in review

All code in the post was tested in FSI. Disclaimer: samples are pure-synthetic, all coincidences with real-life problems are accidental.

Functions, that run computations

"async" builder just describes asynchronous computations without running it so somebody has to do the first step and start execution. Async module provides several functions that accept definition of async workflow and run it.

  • Async.Start – starts execution of the workflow using thread from thread pool
  • Async.StartImmediate – starts execution of the given workflow in a current thread
  • Async.RunSynchronously – starts execution of the given workflow and waits its completion
  • Async.StartWithContinuations – - starts execution of the given workflow and after its completion passes result/exception/OperationCancelledException to one of specified functions. If thread, that initiates executes has SynchronizationContext installed, then final continuations will use this SynchronizationContext for posting results
  • Async.StartAsTask – starts execution of the given workflow and returns System.Threading.Tasks.Task object that can be used to monitor execution status and receive result. Task should be treated in the same way as a task obtained from TaskCompletionSource.Task property – it is not execute the action itself and can be used for checking status and getting result. This function can be used to for interoperability between F# and other languages

Receiving results

Async.Start and Async.StartImmediate execute computations asynchronously so computation process should itself define ways for communication and returning final result. Async.RunSynchronously waits till workflow is finished and returns its result. Async.StartWithContinuations takes continuation for result as one of arguments. When using Async.StartAsTask, result can be taken in many ways (i.e. Task.ContinueWith). For more information please refer official TPL documentation.

Code samples

up to menu

Functions, that construct computations

Computation can be defined not only with async {} builder syntax. It is also possible to create them with the two functions of Async module.
  • Async.FromContinuations – this function accepts another function f with a tuple of 3 continuations as argument: success continuation, error continuation and cancel continuation and returns computation with f as a body. F can contain arbitrary code, but result/error and cancellation should be reported through appropriate continuations. If no continuations were invoked –then this shall be the final step of the workflow
  • Async.FromBeginEnd – Begin/End+IAsyncResult is a common pattern for running asynchronous operations in .NET. FromBeginEnd acts as an adapter for async workflow interface by wrapping provided Begin/End method. Thus it allows using large number of existing components that supports asynchronous mode of work

Code samples

up to menu

Cancellation-related functions

Possibility to cancel task or a sequence of tasks is very important when dealing with asynchronous operations. Async module use the same approach as TPL – usage of CancellationTokenSource/CancellationToken. Async module contains default instance of CancellationToken that all executing opetations share by default, but all operations from “running computation” category accept personal CancellationToken as an optional parameter. Using combination of CancellationTokenSource/CancellationToken is widely covered in the ParallelExtension samples. Library code performs intermediate cancel request checks so developer doesn’t need to do the same in workflow definition. Other functions used in workflow may accept cancellation token as an argument and do checks explicitly. Cancellation related API in Async module provides access to default cancellation token and to token assigned to workflow.

  • Async.DefaultCancellationToken - returns default cancellation token
  • Async.CancelDefaultToken - signals cancellation on default token. Usually used outside the workflow
  • Async.CancellationToken - get the async computation that returns cancellation token for the current workflow. This function is usually used inside workflow to run operations that should be cancelled simultaneously with the workflow
  • Async.OnCancel - gets the computation that installs a cancellation handler.Handler will be executed if cancellation request is signaled. OnCancel returns Async<IDisposable> so common usage is use! _ = Async.OnCancel(..) or let! handler = Async.OnCancel(…) ... handler.Dispose()

Code samples

up to menu

Creating adapters for interoperability with other languages

Often it is very useful to consume workflow functionality outside F#. Async.AsBeginEnd gives possibility to expose workflow as a triple of methods (Begin/End/Cancel) so it can be easily used from other .NET languages. In one of my next posts I’ll show how to extend Async with method that creates event-based adapter for workflow.

Code samples

up to menu

Coordination functions

Async module function that are essential for tasks coordination

  • Async.AwaitEvent - creates a computation that subscribes on given event and resumes execution when event is raised. Returns instance derived from EventArgs
  • Async.AwaitIAsyncResult - creates a computation that waits on provided IAsyncResult. Returns true if IAsyncResult issues a signal within given timeout. Timeout parameter is optional, default value is -1 of Timeout.Infinite
  • Async.AwaitWaitHandle - creates computation that waits on provided WaitHandle. Returns true if handle issues a signal within given timeout. Timeout parameter is optional, default value is -1 of Timeout.Infinite
  • Async.AwaitTask - creates computation that waits on provided task and returns its result

One method is a bit special here - Async.Sleep.It doesn’t wait on anything, instead it allows pausing execution and resuming it on timeout. This operation will not block any operation system threads, but use System.Threading.Timer for scheduling

Code samples

up to menu

Modifiers

This term is arguable, and most likely it will be changed :) With it I'd like to define functions that manipulate over one or many async computations and produce new enhanced computation.

  • Async.Parallel - – takes a sequence of async computation and returns workflow, that will execute source computation in a fork-join way. Results array of results
  • Async.Catch - takes a computation and returns workflow that intercepts all exceptions within source computation. Result – Choice<’T, exn> it can be extracted via pattern matching
  • Async.Ignore - takes a computation and returns workflow that executes source computation, ignores its result and returns unit
  • Async.TryCancelled - takes a computation and a compensating action. Returns workflow that runs source computation. If execution is cancelled before completion, then proceed with compensating action

Code samples

up to menu

Functions that start subcomputation

Async workflow may start subworkflows that are executed simultaneously with the parent.

  • Async.StartChild - produces a computation that starts a given computation and returns computation for receiving result (yes, Async<Async<…>><…> :) ). If parent computation requests the result and child computation is not finished yet – parent computation is suspended until child completion
  • Async.StartChildAsTask - produces a computation that starts a given computation and returns Task instance for monitoring status and receiving results

Code samples

up to menu

Thread selection functions

Sometimes it is necessary to specify explicitly what kind of thread shall be used for the next step of computations.

  • Async.SwitchToThreadPool - creates a computation that executes its continuation in that thread from thread pool
  • Async.SwitchToNewThread - creates a computation that executes its continuation in the new thread
  • Async.SwitchToContext - creates a computation that with post its continuation to the given syncContext

Code samples

up to menu

вторник, 16 февраля 2010 г.

Event-based async pattern in F#

Hi everyone, finally I made up my mind and decided to start blogging:) .

I've played with F# for a long time and would like to say, that I'm very impressed by its power and expressiveness. Many concepts from the language itself and from the standard library provide quick, short and elegant solution for a number of non-trivial problems.

One of such problems is running asynchronous operations. Strictly speaking, execution itself is not a big issue, in a most common case you can just enqueue task in a thread from the threadpool or spawn the worker thread manually. The complexity reveals itself when you need to coordinate execution of many operations, for example running one async operation after another or create a number of child tasks within operation and resume only when all of them are completed. .NET BCL provides two common patterns to deal with asynchronous operations:

1. IAsyncResult result + Begin/End methods

2. Event-based async pattern.

Both of them have similar features: they are awkward when coordination is touched. For IAsyncResult pattern it is possible to utilize callbacks to chain execution of subsequent operations and properties/methods of IAsyncResult object to check execution status, but code looks become unreadable mess. Anonymous methods from C# 3.0 helps a lot but even with them program that sequentially execute number of async operation begins to look like a “ladder” made of lambdas. Event-based async pattern have no coordination support at all.

F# async workflows come to the rescue.

F# standard library contains ultimate weapon to solve problems with async operations: asynchronous workflows. They allow writing code that looks like sequential but is asynchronous under the hood.

   1: let readToEnd (reader : #TextReader) = async {   
   2:     do! Async.SwitchToThreadPool()
   3:     return reader.ReadToEnd()
   4:     }
   5:  
   6: let loadPageAsync (url : string) = async {
   7:     let request = WebRequest.Create(url) :?> HttpWebRequest
   8:     let! response = request.AsyncGetResponse()
   9:     use stream = response.GetResponseStream()
   10:     use reader = new StreamReader(stream)
  11:     let! content = readToEnd reader
  12:     return content
  13:     }

Workflow description is sliced into pieces and each piece can be executed asynchronously one after another.

sliced_workflow

Most features of creating async workflows and using Async module shall be covered in subsequent posts, for now I’ll speak about integration with existing patterns of asynchronous execution.

Workflows can be created based on Begin/End methods via calling Async.FromBeginEnd

   1: use stream = response.GetResponseStream()
   2: let buf = Array.zeroCreate 8192
   3: let! read = Async.FromBeginEnd(buf, 0, buf.Length, stream.BeginRead, stream.EndRead)

Also workflow functionality can be exposed to other .NET languages as a pair of Begin/End methods

   1: type PageDownloader() = 
   2:     let beginMethod, endMethod, cancelMethod = Async.AsBeginEnd(loadPageAsync)
   3:     member this.Begin = beginMethod
   4:     member this.End = endMethod
   5:     member this.Cancel = cancelMethod

Unfortunately Async workflows doesn’t support event-based async pattern but Async module has method AwaitEvent that can be used. 

   1: type OperationCompletedEventArgs(r : string) = 
   2:     inherit System.EventArgs()
   3:     member this.Result = r
   4:  
   5: type Class() = 
   6:     let completed = new Event<System.EventHandler<OperationCompletedEventArgs>, _>()
   7:     member this.RunOperationAsync() = 
   8:         // do some job
   9:         completed.Trigger(box this, new OperationCompletedEventArgs("done"))
  10:         ()
  11:     [<CLIEvent>]
  12:     member this.OperationCompleted = completed.Publish
  13:  
  14: let runJob (c : Class) = async {
  15:     do c.RunOperationAsync()
  16:     let! res = Async.AwaitEvent(c.OperationCompleted)
  17:     return res.Result
  18:     }
  19:  

Do not use this code, it it buggy!!!

Async.AwaitEvent method is blocking so we cannot call it before RunOperationAsync. If we simply change order (like in the sample) this will lead to potential race condition - operation can be finished before Async.AwaitEvent subscribes to OperationCompleted event and AwaitEvent will wait till the end of times. To solve this we need something similar to Subject in Rx: something that can subscribe to OperationCompletedEvent before RunOperationAsync and reraise event if situation described above occurred. Let’s name it Subject too.

   1: // sketch of implementation
   2: open System
   3:  
   4: [<AutoOpen>]
   5: module AsyncExtensions =
   6:     type private DelegateAdapter<'T>(o : IObserver<_>) = 
   7:         static let invokeMethod = typeof<DelegateAdapter<'T>>.GetMethod("Invoke") 
   8:         static let argumentTypes = invokeMethod.GetParameters().[1..] |> Array.map (fun p -> p.ParameterType)
   9:         
  10:         static member Method = invokeMethod
  11:         static member ArgumentTypes = argumentTypes
  12:  
  13:         member this.Invoke(args : 'T) = o.OnNext(args)
  14:  
  15:     type Async with
  16:         static member GetSubject(event : IEvent<'D, 'T>) = 
  17:             let value = ref None
  18:             let handlers : Delegate ref = ref null
  19:  
  20:             let sync = new obj()
  21:  
  22:             let trigger () = 
  23:                 match !handlers, !value with
  24:                 | l, Some(v) -> 
  25:                     match DelegateAdapter<_>.ArgumentTypes.Length with
  26:                     | 1 -> l.DynamicInvoke([| null; box v |]) |> ignore
  27:                     | _ -> l.DynamicInvoke(Array.append [|null|] (Microsoft.FSharp.Reflection.FSharpValue.GetTupleFields(box v))) |> ignore
  28:                 | _ -> ()
  29:  
  30:             let rec dispose = event.Subscribe(handler)
  31:             and handler v = lock sync (fun () ->
  32:                 value := Some(v)
  33:                 trigger()
  34:                 dispose.Dispose()
  35:             )
  36:  
  37:             let addHandler h = lock sync (fun () -> 
  38:                 handlers := h; 
  39:                 trigger() 
  40:                 )
  41:             let removeHandler h = lock sync (fun () -> handlers := null)
  42:  
  43:             { new IEvent<'D, 'T> 
  44:               interface IDelegateEvent<'D> with
  45:                     member this.AddHandler(h) = addHandler h
  46:                     member this.RemoveHandler(h) = removeHandler h
  47:               interface IObservable<'T> with
  48:                     member this.Subscribe(s) = 
  49:                         let adapter = new DelegateAdapter<_>(s)
  50:                         let handler = Delegate.CreateDelegate(typeof<'D>, adapter, DelegateAdapter<'T>.Method)
  51:                         addHandler handler
  52:                         { new IDisposable with
  53:                             member d.Dispose() = removeHandler handler } }

Subject subscribes to original event and in simple case just delegates call to inner handler (AwaitEvent subsriber). If event is raised when AwaitEvent handler is not attached yet, then result is stored in field and AwaitEvent handler receives it in moment of subscription.

   1: let runJob2 (c : Class) = async {
   2:     let subject = Async.GetSubject(c.OperationCompleted) // s
   3:     do c.RunOperationAsync()
   4:     let! res = Async.AwaitEvent(subject)
   5:     return res.Result
   6:     }
 
GeekySpeaky: Submit Your Site!