The Async workflow uses the threadpool which is a system-managed queue of work items and a set of threads on which to run them. When you use ReadAsync, after starting the asynchronous I/O operation the current work item terminates allowing the threadpool thread it was runnning on to start another work item from the queue. When the asynchronous I/O operation is completed a new work item is scheduled to continue the workflow that requested the operation. In this way there can be more images concurrently being processed than there are threads in the threadpool.

By on 11/14/2008 2:51 PM ()

The Async workflow uses the threadpool which is a system-managed queue of work items and a set of threads on which to run them. When you use ReadAsync, after starting the asynchronous I/O operation the current work item terminates allowing the threadpool thread it was runnning on to start another work item from the queue. When the asynchronous I/O operation is completed a new work item is scheduled to continue the workflow that requested the operation. In this way there can be more images concurrently being processed than there are threads in the threadpool.

I see, that's sort of what I was getting at with "more granular": the tasks defined using ReadAsync can be split more effectively by the system.

However, while I see that indeed, more images can be "being processed" at the same time, I'm not sure I get how that would increase the throuphput, or imrpove the overall processing time. If there are 100 threads available, and there are say, 1000 images to process, it doesn't really matter whether you read-process-write the images in 10 batches of 100 images (the synchronous case) or if you read a 100, read another 100, process 50, write 50, read another 100, and so on. If anything, I would expect it to go slower because there are more context-switches.

Anyway, I'll see if I can try some experiments somewhere next week. Right now, I'm on my old single core, single CPU, 5 year old laptop where _everything_ is slow ;)

This is all very subtle in any case. I think a much more convincing argument could be made with an example using asynchrony for keeping the UI reactive. Anyone who has done UI's using Winforms or WPF knows the pain involved in that, and it sure seem like Asyn workflows are able to clean up much of that mess.

Kurt

By on 11/15/2008 4:43 AM ()

Brian's example is illuminating of the difference. Essentially in the Async model pending I/O operations don't block any thread, therefore the computation can always progress. This can be subtle to appreciate when the Async computation runs on the threadpool because the threadpool is already a large collection of threads so it dilutes the new form of concurrency the Async computation is inroducing. In theory the Async computation only requires one thread to execute on, whereby all I/O operations are multiplexed on this one thread. You might want to take a look at my blog post that describes building an async computation engine that runs on a single thread instead of the threadpool.

Re Async workflows and UI programming. I've never tried this myself but I would guess you would:
1. Write your event handler as an Async<unit> computation.
2. Run this computation in the event handler.

1
btn.Click.Add(fun _ -> Async.Run (* ... *))

3. Use the SwitchTo operations to move between background and UI. E.g.,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type Async =
  let SwitchToWinForms () = Async.SwitchTo (new WindowsFormsSynchronizationContext())
async {
  // execution starts in UI thread
  // update some UI
  btn.Enabled <- false

  // go to background thread
  Async.SwitchToThreadPool ()


  // do lots of work
  Process1000Images ()

  // go back to UI thread
  Async.SwitchToWinForms ()
  btn.Enabled <- true }
By on 11/15/2008 8:20 PM ()

Thank you both for the illuminating discussion. I can't say I really pieced together all the parts of the puzzle yet, but I see light at the end of the tunnel ;)

Thanks for the blog post heads up. I remember reading the paper that it is based on a long time ago, I never made the connection with Async workflows however.

Finally, on UI programming, I was thinking more along the lines of (haven't tested this at all!):

1
2
3
4
5
6
7
8
9
10
async {

//execution on UI thread
btn.Enabled <- false

//do some work in background thread
let! result = model.DoLongCalculationAsync(btn.Input)

//do something with result in UI thread
} 

Where somewhere in the model.DoLongCalculationAsync I'd probably have to use SpawnThenPostBack to put the result back on the UI thread. Maybe a new async variant would be useful to do this automatically, i.e. run async calls on the threadpool and return in the syncrhonization context of the caller.

Don't know if I'm making sense here.

thanks!

Kurt

By on 11/16/2008 1:03 AM ()

Yes, SpawnThenPostBack is another good idea. The library documentation hints at using that function for this purpose.

By on 11/16/2008 12:31 PM ()

What happens if there are only 100 threads available, your network/disk connection is slow, and all 100 threads get blocked waiting on I/O? Now your CPU is idle while you wait for data to trickle from the disk/network. With ReadAsync(), on the other hand, even if the I/O itself is slow, there are always free threads available to process the 'real work' with the CPU.

By on 11/15/2008 11:07 AM ()

You're right that there are two aspects here. There's "using Async so that you can run operations in parallel", and there's "using Async so that you don't block a thread while waiting on IO". If you are doing tons of reads in parallel and the Read()s are not ReadAsync()s, then I think either (1) the threadpool will throttle the number of threads it creates and you'll end up having e.g. the 100th Read() operation block because there there are no more threads available in the thread pool (they're all block on Read()s), or (2) the threadpool allocates tons and tons of threads, which will keep the parallelism going but impact performance because threads consume memory and take cycles to create and destroy.

So the ThreadPool is a resource, and ReadAsync() (as opposed to Read()) will remove some needless pressure against that resource.

That's my high-level understanding, anyway; it might be interesting to measure trying to read 10000 things using both strategies.

By on 11/14/2008 12:41 PM ()
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