7

I created a library that is a invoker for a web service somewhere else. The library exposes asynchronous methods, since web service calls are a good candidate for that matter.

At the beginning everything was just fine, I had methods with easy to understand operations in a CRUD fashion, since the library is a kind of repository.

But then business logic started to become complex, and some of the procedures involves the chaining of many of these asynchronous operations, sometimes with different paths depending on the result value, etc.. etc..

Suddenly, everything is very messy, to stop the execution in a break point it is not very helpful, to find out what is going on or where in the process timeline have you stopped become a pain... Development becomes less quick, less agile, and to catch those bugs that happens once in a 1000 times becomes a hell.

From the technical point, a repository that exposes asynchronous methods looked like a good idea, because some persistence layers could have delays, and you can use the async approach to do the most of your hardware. But from the functional point of view, things became very complex, and considering those procedures where a dozen of different calls were needed... I don't know the real value of the improvement.

After read about TPL for a while, it looked like a good idea for managing tasks, but in the moment you have to combine them and start to reuse existing functionality, things become very messy. I have had a good experience using it for very concrete scenarios, but bad experience using them broadly.

How do you work asynchronously? Do you use it always? Or just for long running processes?

6
  • Have you considered async-await?
    – svick
    Commented Jun 25, 2013 at 17:30
  • Yes, but we do not use that version of .NET yet, and it only simplifies the top code, but the debugging is same has hard. Commented Jun 26, 2013 at 14:36
  • 2
    1. You you can use async-await with .Net 4.0 (but you will need VS 2012). 2. Debugging is simpler, because the debugger understands async-await, so you can debug it more or less like normal synchronous code.
    – svick
    Commented Jun 26, 2013 at 16:34
  • 2
    Approach that worked best for me so far is The way of the Future
    – gnat
    Commented Jun 27, 2013 at 13:22
  • @svick I just installed to see how it goes, Thanks. Commented Jun 28, 2013 at 19:36

4 Answers 4

7

I would design a state machine for each set of related, linked processes, and keep track of the state somewhere in a high level object, so it is easier to debug. State transitions can be traced, and invalid state transitions can be detected.

2
  • 1
    It seems like you have to be sure the asynchronous processing worth the extra development time before start. Probably caring about making your overall design scalable is more worth that adding complexity to the code. I mean, probably adding another box to the system will be cheaper than expend X time supporting asynchronous calls. What do you think? Commented Jun 28, 2013 at 19:45
  • Excellent point. It may be helpful to bundle several operations together to be executed synchronously or asynchronously as a group. If each operation depends on the previous result, there is no gain in separating them into individual asynchronous operations. I don't think adding another box makes it easier to design... that is more pain usually, at least for me. Commented Jun 29, 2013 at 17:14
3

As Frank says, write a state machine for each chunk of business logic that requires multiple async calls. Then, it's easy to interrogate the state when debugging, because you enumerated it.

In fact, start by just drawing out the state diagram. If the diagram is too complex (especially if you have multiple async calls in-flight which could return in various orders), write it out as a table to make sure you handled every possible combination.

1
  • 1
    Well if it is too complex, there are other options. Perhaps break into multiple state machines, or super and sub states. Commented Jun 28, 2013 at 18:30
1

First - you plan out and develop a synchronous code that works perfectly alone. Then you isolate parts which should be re entrant and create access points for those parts of code.

This approach is best, because you will only add synchronization mechanism on demand.

1
  • 1
    Unfortunately that does not fit what employers expect from the software development process. I have to do my best guess before start :D I always say that it would be great if we knew at least the 80% of the project details before start, never happened. Commented Jun 28, 2013 at 19:39
1

If you have the ability to use coroutines (with yields at arbitrary depths of the call-stack) or continuations, these allow you to write asynchronous code that looks very much like synchronous code; all an asynchronous call has to do to look synchronous is to suspend the execution after doing the call and schedule the resumption of execution for when the response to the asynchronous call becomes available. This greatly reduces the overall apparent complexity. (The complexity doesn't actually go away, but the language/runtime handles the majority of the tricky parts so you don't have to.)

It's possible to simulate this sort of thing with threads. That always leaves me feeling a bit dirty though, as I consider having lots of I/O-bound threads to be an indication that the system design is not right.

Failing that, your best approach is to use continuation-passing style in which you split your code up into synchronous pieces and pass any necessary shared state between them (across the periods when you're waiting for the asynchronous processing to produce a result) using a context object of some sort. This is a little bit awkward to understand in abstract: here's a little bit more concrete example. Consider this synchronous (pseudo-)code:

function DoWork() {
    let A = "foo";
    let B = 1.23;
    Call1(the.arguments);
    A.append(B);
    Call2(the.arguments);
    B += A.length;
}

DoWork();
print "finished";

If we change this to continuation-passing style, we get something like this:

function DoWork(doneCallback) {
    let state = new Context();
    state.A = "foo";
    state.B = 1.23;
    state.done = doneCallback;
    DoAsyncCall1(the.arguments, callback=fn()=>DoMoreWork(state));
}
function DoMoreWork(state) {
    state.A.append(state.B);
    DoAsyncCall2(the.arguments, callback=fn()=>DoneTheWork(state));
}
function DoneTheWork(state) {
    state.B += state.A.length;
    state.done(state.A, state.B);
}

DoWork(fn(…)=>print "finished");

Each section between the to-be-asynchronous calls becomes its own function (you should also make the start of any loop you've got — provided it has an async call in it — be the start of a function so that you can model the loop by calling back). The state that is getting passed around is the key to making this all work, and it's possible (assuming a non-horrific asynchronous call mechanism) to then have multiple sets of processing going on at once, as each part only knows about its little world. It is, however, important to try to avoid using global state when doing this, if for no other reason than it will leave you very confused. It's much easier to make this all work if you are using largely functional programming techniques.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.