2013 Edit: async
and await
now make this trivial! :-)
I've got some code that screen scrapes a website (for illustrative purposes only!)
public System.Drawing.Image GetDilbert()
{
var dilbertUrl = new Uri(@"http://dilbert.com");
var request = WebRequest.CreateDefault(dilbertUrl);
string html;
using (var webResponse = request.GetResponse())
using (var receiveStream = webResponse.GetResponseStream())
using (var readStream = new StreamReader(receiveStream, Encoding.UTF8))
html = readStream.ReadToEnd();
var regex = new Regex(@"dyn/str_strip/[0-9/]+/[0-9]*\.strip\.gif");
var match = regex.Match(html);
if (!match.Success) return null;
string s = match.Value;
var groups = match.Groups;
if (groups.Count > 0)
s = groups[groups.Count - 1].ToString(); // the last group is the one we care about
var imageUrl = new Uri(dilbertUrl, s);
var imageRequest = WebRequest.CreateDefault(imageUrl);
using (var imageResponse = imageRequest.GetResponse())
using (var imageStream = imageResponse.GetResponseStream())
{
System.Drawing.Image image_ = System.Drawing.Image.FromStream(imageStream, true /*useEmbeddedColorManagement*/, true /*validateImageData*/);
return (System.Drawing.Image)image_.Clone(); // "You must keep the stream open for the lifetime of the Image."
}
}
Now, I would like to call GetDilbert() asynchronously. The easy way to use a delegate:
Func<System.Drawing.Image> getDilbert;
IAsyncResult BeginGetDilbert(AsyncCallback callback, object state)
{
getDilbert = GetDilbert;
return getDilbert.BeginInvoke(callback, state);
}
System.Drawing.Image EndGetDilbert(IAsyncResult result)
{
return getDilbert.EndInvoke(result);
}
While that certainly works, it isn't very efficient as the delegate thread will spend most of its time waiting for the two I/O operations.
What I would like to do is to call request.BeginGetResponse()
, do the regex match, and then call imageRequest.BeginGetResponse()
. All while using the standard async call pattern and preserving the signatures of BeginGetDilbert() and EndGetDilbert().
I've tried several approaches and haven't been completely satisfied with any of them; this seems to be a royal pain. Hence, the question. :-)
EDIT: Its seems that the approaches using iterators are frowned on by the C# compiler team.
A plea from the compiler team:
Though it is assuredly the case that you CAN use iterators to implement state machines, poor-mans coroutines, and so on, I wish people would not do so.
Please use tools for the purposes for which they were intended. If you want to write state machines, write yourself a library that is designed specifically to solve that general problem and then use it.
Using tools for purposes other than what they were intended for is "clever", and clever is bad; clever is hard for maintenance programemrs to understand, clever is hard to extend, clever is hard to reason about, clever makes people think "out of the box"; there's good stuff in that box.
Going with the Future<>
answer because that stays in C# which is the same as my sample code. Unfortunately, neither the TPL nor F# are officially supported by Microsoft...yet.
It is kind of a nightmare to get this right. You need to create callbacks to pass into each 'Begin' method that then run the 'continuation' of the method. (And don't forget to ensure all the exception-handling and CompletedSynchronously logic is correct!) When you author this in C# today, your code turns into a hopeless mess of spaghetti, but that's about the only way you can achieve your goal (not have threads blocking on I/O waits).
On the other hand, if it's within reason for your situation, F# makes this very simple and straightforward to author correctly. See this video (namely, 8 minutes starting at 52:20) for a synopsis.
EDIT
to answer Dan's comment, here is a very rough sketch... I pulled it from an email I wrote in outlook, I doubt it compiles. The exception paths are always gnarly, so be careful (what if ‘cb’ throws?); you may want to find a rock-solid AR/Begin/End implementation in C# somewhere (I dunno where, I’m sure there must be many) and use it as a model, but this shows the gist. The thing is, once you author this once, you have it for all time; BeginRun and EndRun work as the 'begin/end' on any F# async object. We have a suggestion in the F# bug database to expose the Begin/End APM on top of async in a future release of the F# library, so as to make it easier to consume F# async computations from traditional C# code. (And of course we're striving to work better with 'Task's from the parallel task library in .Net 4.0 as well.)
type AR<’a>(o,mre,result) =
member x.Data = result
interface IAsyncResult with
member x.AsyncState = o
member x.AsyncWaitHandle = mre
member x.CompletedSynchronously = false
member x.IsCompleted = mre.IsSignalled
let BeginRun(a : Async<’a>, cb : AsyncCallback, o : obj) =
let mre = new ManualResetEvent(false)
let result = ref None
let iar = new AR(o,mre,result) :> IAsyncResult
let a2 = async {
try
let! r = a
result := Choice2_1(r)
with e ->
result := Choice2_2(e)
mre.Signal()
if cb <> null then
cb.Invoke(iar)
return ()
}
Async.Spawn(a2)
iar
let EndRun<’a>(iar) =
match iar with
| :? AR<’a> as ar ->
iar.AsyncWaitHandle.WaitOne()
match !(ar.Data) with
| Choice2_1(r) -> r
| Choice2_2(e) -> raise e
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With