Java Reference
In-Depth Information
The story often goes something like this. Let's say you have a method that is going to do some expensive
work, such as going to the database. Instead of having your code block for the trip, you have the user pass
you a callback that executes when the data returns. The code looks something like this:
public void readData(String key, ResultsHandler handler) {
threadPool.submit(() -> {
SomeModel resultingModel = null;
/* Do expensive query setting "resultingModel"... */
handler.accept(resultingModel);
}
);
}
Later on, you decide to add a caching layer in front of this method, because that is even more efficient,
and some slightly stale data is just fine. So you update your code to read something like this:
public void readData(String key, ResultsHandler handler) {
SomeModel cachedModel = cache.get(key);
if (cachedModel != null) {
handler.accept(cachedModel);
} else {
threadPool.submit(() -> {
SomeModel resultingModel = null;
/* Do expensive query setting "resultingModel"... */
cache.put(key, resultingModel);
handler.accept(resultingModel);
}
);
}
}
Now we're mixing asynchronous and synchronous styles: the handler may be executed either in the
current thread (before we return) or in another thread (at some arbitrary point). Zalgo has been released.
I have told you that it's horrifically evil, but it seems innocuous enough. If it is going to block the thread for a
long time, it executes the code in another thread. If it won't block, it executes in this thread. Seems helpful.
What's the problem with that?
That, my child, is how Zalgo will consume you.
The problem is that you don't know how the callback code will behave. The handler can do whatever
it wants, including doing very expensive blocking computations. If the method resolves asynchronously
— as it did before — then that is fine, since the primary execution thread of your application will continue
along. Now that you changed the method to sometimes resolve asynchronously but sometimes resolve
synchronously, that means that it will now sometimes block your primary execution thread with very
expensive blocking computations. Sometimes it won't, of course. This is going to make for an infuriating
debugging experience.
When the developer reasons about this code, mixing asynchronous and synchronous execution creates
a branch with two very different but equally possible realities: one where the code executes synchronously
and one where the code executes asynchronously. The way the application behaves in these two cases can
vary wildly, so the developer has to hold both possibilities in mind. Each time the developer encounters this
code, it creates another fork, doubling the complexity of the application. In some cases, both these branches
are pretty much the same, but mark my words: the differences in those branches will crop up in surprising,
unfortunate, and hard-to-debug circumstances.
Search WWH ::




Custom Search