Synchronicity

Federico, it’s funny how unrelated threads of converge sometimes. The last couple of day’s I’ve working on trying to improve some behavior in F-Spot that is both processor and I/O intensive.

The Problem
There is a file loader in F-Spot that is used to load most images for display. It knows how to load some things progressively and others in a single pass. Sometimes for some files it would block for a long time while loading the image and this wasn’t acceptable. It is important that F-Spot is as responsive as possible to user input.

A Solution?
I started to work on the problem by converting a couple of problem loaders used blocked waiting for complete input into into incremental loaders that could operate asynchronously. This helped quite a bit, the loader would read some then process some and occationally return to the mainloop to process requests things were looking good.

Good except when it didn’t work. It turns out that in some cases the source stream was actually a pipe, and that pipe would often not have any data available for several seconds. So even though the loader was switching back and forth between reading, processing the image and yielding to the mainloop, if it ran into a one of those streams it would try to read from it and stop everything until something was written.

Async I/O to the rescue. After all it seems a like a good fit, we’ll start a read, get notification when it is done use that in the loader and be in good shape. Fortunately the .Net stream class has a nice built in interface for async I/O so the platform is even going to help me out. Now instead of calling stream.Read I call stream.BeginRead with a callback function that lets me know when the read has completed, then I add some support code to handle passing the read data back to the mainloop (the only place we can safely use it) and everything should work.

It should work, but it doesn’t. It turns out that for whatever reason (I honestly don’t know the details) System.IO.FileStream.BeginRead isn’t actually async in mono 1.x. It also blocks waiting for something to be written to the pipe. After some discussion with Gonzalo he schools/reminds that all c# delegates have asynchronous BeginInvoke/EndInvoke methods. So a quick wrapper later I have a stream that actually does async I/O, and everything mostly works.

Deadlock

So at this point the goal has been largely met, but at what cost? The complexity of doing everything incrementaly runs pretty deep and it turns out I haven’t actually avoided using threads (BeginInvoke uses a thread). I don’t think you can generalize this example to larger statement about when to use threads versus incremental apis and async I/O because it’s hard to draw general conclusions from a specific case. It is doubly hard when that case includes platform specific problems. If I’d been doing this all in C the problems and potential solutions would have been slightly different.

It does however, make two things clear to me: complicated things are complicated no matter how you slice them, and I’m not nearly as smart as I’d like to be.

Comments are closed.