There's no such thing as single-threaded async i/o on the CLR because the callback will always be called on an i/o completion thread of the thread pool. The EAP pattern (as implemented by WebClient and others) is kind enough to automatically schedule the event raising using SynchronizationContext.Current, but APM and TAP won't do this for you on their own.

By on 11/14/2010 3:33 AM ()

I find that odd since you can do single-threaded async i/o on Linux using libevent. Are you sure this can't be done on .NET? Single threaded programs are easier to reason about since you don't have to worry about locking writes to shared data.

By on 11/14/2010 3:46 AM ()

You can always do the rescheduling yourself using SynchronizationContext.Send/Post, Task.ContinueWith(..., TaskScheduler.FromCurrentSynchronizationContext()) or do! Async.SwitchToContext in an async computation expression.

/edit: If I'm not mistaken (no F# in reach to test it;) ), under certain circumstances async expressions will inherit the SynchronizationContext and use it to schedule the continuation - just as the C# 5.0 CTP does:

1
2
3
4
async {
    let! response = request.GetResponseAsync()
    doSomethingWith response // still on UI thread
} |> Async.StartImmediate
By on 11/14/2010 4:07 AM ()

It might be clearer if I posted the code I have. I did try to figure out how to do what I wanted using the Task classes but couldn't. Note that I don't want to use async workflows.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
open System
type RequestState = { 
request : Net.WebRequest; 
    url: string 
}

let get_response_cb (result : IAsyncResult) =
  let tid = string(Threading.Thread.CurrentThread.ManagedThreadId)  
  let rs = result.AsyncState :?> RequestState
  use response = rs.request.EndGetResponse(result)  
  Console.WriteLine ("Thread id = " + tid + ", Response for " + rs.url)    
  use stream = response.GetResponseStream()
  use reader = new System.IO.StreamReader(stream)
  let html = reader.ReadToEnd()
  ()

let begin_get_response (url : string) =
  let wr = Net.WebRequest.Create url
  let cb = new AsyncCallback(get_response_cb)
  let result = wr.BeginGetResponse(cb, {request =  wr; url = url})
  let tid = string (Threading.Thread.CurrentThread.ManagedThreadId)
  Console.WriteLine ("Thread id = " + tid + ", Request for " + url)
  ()

begin_get_response "http://www.google.com"

In this setup begin_get_response can be called multiple times within a loop without it blocking while it waits for a response because the response is delegated to a callback. In the .NET framework the callback function is called run within a different thread that is created implicitly. The question is how to convert these functions so that the callback is run within the same thread as the rest of the program.

By on 11/16/2010 5:05 AM ()

About the indentation:

I tried to fix it but couldn't. begin_get_response and get_response_cb are functions so need indentation. The type RequestState needs indentation as well.

By on 11/16/2010 5:19 AM ()

Here's a simple WinForms app that demos it. Async.Parallel starts a bunch of asyncs in the threadpool, but the first thing each async does is return to the SynchronizationContext (UI thread) and so all its work happens on the UI thread.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
open System
open System.Net
open System.Threading
open System.Windows.Forms

let urls = [ "http://bing.com"; "http://cnn.com" ]

let form = new Form()
let textBox = new TextBox(Text = "loading pages...", Multiline=true, Width=300, Height=300)
form.Controls.Add(textBox)
form.Load.Add(fun _ ->
	let ctxt = SynchronizationContext.Current 
	seq { 
			for u in urls do
				yield async {
					do! Async.SwitchToContext(ctxt)
					textBox.Text <- textBox.Text + sprintf "\r\n(%d) about to start fetching %s" Thread.CurrentThread.ManagedThreadId u
					let wr = WebRequest.Create(u)
					let! resp = wr.AsyncGetResponse()
					textBox.Text <- textBox.Text + sprintf "\r\n(%d) %s: %d" Thread.CurrentThread.ManagedThreadId u resp.ContentLength 
				}
	} |> Async.Parallel |> Async.Ignore |> Async.Start
)

[<STAThread>]
do
		Application.Run(form)
By on 11/16/2010 10:55 AM ()

Thanks but that's still technically multithreaded. Each async workflow will intiialy start in it's own thread allocated from the thread pool. This is why I specified that I did not want to use async workflows. I realize that the sample posted is logically single threaded but any correct multi-threaded program will be logically single threaded.

I'm just after an example single-threaded program that demonstrates asynchronous i/o on .NET. It doesn't have to use a WebRequest to do it if the WebRequest class makes it too hard to do. It can use another class, like the Socket class, if that class makes it easier. This model, i.e. single threaded async i/o, for writing programs is in use on older operating systems that don't support threads.

By on 11/16/2010 5:37 PM ()

Thanks for the clarification.

What's wrong with a single async and StartImmediate then?

1
2
3
4
5
async {
    blah blah sync
    let! html = AsyncWebWhatever
    more sync
} |> Async.StartImmediate

all runs entirely on the UI thread. The Async call returns all the way back down to the message pump, and when the IO returns, it posts work to the pump which restores this context and keeps going.

By on 11/17/2010 1:42 AM ()

Thanks, that's great. StartImmediate did the trick and let me have single-threaded program with multiple request in flight at once.

By on 11/17/2010 3:04 AM ()

May have spoken too soon ... When the response is returned the original context is not restored.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
let st_request (url : string) max_urls (count : int ref) 
    (finished : Threading.ManualResetEvent) =  
    async {    
      let tid = string(Threading.Thread.CurrentThread.ManagedThreadId)  
      let wr = Net.WebRequest.Create url
      Console.WriteLine("Thread id = " + tid + ", Request for " + url)
      use! response = wr.AsyncGetResponse()    
      count := !count + 1
      let tid = string(Threading.Thread.CurrentThread.ManagedThreadId)
      Console.WriteLine("Thread id = " + tid + ", Response for " + url)    
      if !count = 6 then 
        finished.Set() |> ignore
     ()
   }

///run the single threaded asynchronous version
let sa_run =
    let finished = new Threading.ManualResetEvent(false)
    let count = ref 0
    let ctxt = Threading.SynchronizationContext.Current 
    (fun (urls : list<string>) ->
      Console.WriteLine("Single-threaded asynchronous requests ...")
      List.iter (fun url -> 
        Async.StartImmediate(st_request url urls.Length count finished)) urls
      finished.WaitOne() |> ignore)

yields the following output:

1
2
3
4
5
6
7
8
9
10
11
12
13
Single-threaded asynchronous requests ...
Thread id = 1, Request for http://www.google.com
Thread id = 1, Request for http://www.microsoft.com
Thread id = 1, Request for http://www.yahoo.com
Thread id = 1, Request for http://www.wordpress.com
Thread id = 1, Request for http://www.blizzard.com
Thread id = 1, Request for http://www.valvesoftware.com
Thread id = 13, Response for http://www.microsoft.com
Thread id = 13, Response for http://www.yahoo.com
Thread id = 14, Response for http://www.valvesoftware.com
Thread id = 10, Response for http://www.google.com
Thread id = 10, Response for http://www.blizzard.com
Thread id = 13, Response for http://www.wordpress.com
By on 11/20/2010 8:25 PM ()

This technique only works if you are starting on a thread with a synchronization context and a message pump, e.g. the UI thread in a WPF/WinForms app. It sounds like maybe you're just doing this straight from a console app at the top-level?

By on 11/20/2010 8:59 PM ()

Yeah that's right - it's just in the console.

By on 11/20/2010 9:45 PM ()

I am very interested in this, too. Did you ever figure it out?

By on 12/24/2010 12:16 PM ()

The code I posted earlier (with Async.StartImmediate on the STA UI thread) does it. You have to have a message pump, else it doesn't make much sense, really.

By on 12/24/2010 12:56 PM ()

In addition, is it possible to use MailboxProcessors in a single-threaded async manner?

By on 12/25/2010 9:13 PM ()

Thanks, Brian. Does that make sense for a console app or a Windows Service? Is there another way to create a "message pump" other than a WinForms app?

I'm interested in leveraging F#'s async for a performant server platform. My interest in single threading is related to this series of responses on Twitter:

1
2
3
http://twitter.com/bvanderveen/status/17683108828549120
http://twitter.com/bvanderveen/status/17683439473926144
http://twitter.com/bvanderveen/status/17683861236355073

Is that logic flawed? Is the difference marginal?

Thanks,

Ryan

By on 12/25/2010 10:37 AM ()

If it was such a bad idea, why would Microsoft ever have implemented Async Pages? If half of the processing time of requests is IO-bound, using async IO and a threadpool you can achieve a ratio of one thread per two requests - sounds like a good idea to me.

By on 12/26/2010 2:21 AM ()

Every async wrapper function in the F# library (e.g. AsyncSleep, AsyncGetResponse, etc) will capture and return you to a synchronization context, if there is one.

(If you build your own asyncs out of primitives like FromBeginEnd or FromContinuations, then it's up to you to do this on your own if you want that behavior.)

Also note that all asyncs are 'logically-single-threaded' (unless doing explicit concurrency like Async.Parallel), it's just that you may hop from e.g. threadpool thread 1 to threadpool thread 2 at 'bang' points.

By on 11/14/2010 10:51 AM ()
IntelliFactory Offices Copyright (c) 2011-2012 IntelliFactory. All rights reserved.
Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us | Terms of Use | Privacy Policy | Cookie Policy
Built with WebSharper