2

I'm a programmer working on a project using .Net 4.0 and trying to figure out the best way to combine a loosely coupled and extensible design with the fact that the same logical operation may sometimes be performed asynchronously and sometimes only synchronously (every implementation that supports async also supports sync, but not the other way around). In this project, every such logical operation is represented by an interface (the loose coupling and extensibility demands are high). Let's focus my question around a specific interface: IDataDictLoader. This interface is responsible for loading an object called DataDict. There are two possible implementations for this interface: one called LocalDataDictLoader which uses local dlls and performs the operation synchronously (returning DataDict), and the other called WebServiceDataDictLoader which uses a web service and performs the operation asynchronously (returning Task<DataDict>) or synchronously. A value in a configuration file determines the implementation to create - if the value is "Local" then LocalDataDictLoader is created, if the value is "WebService" then WebServiceDataDictLoader is created. The creation part uses reflection guided by conventions, in the function IDataDictLoader CreateLoader(string configValue). The code calling that function and consuming the interface does not know up ahead what the implementation will be, it doesn't have access to the configuration value even it wanted to know. The question is how to design the interface? As of now I thought of a number of options:

  1. Have two methods on the interface, one sync and one async, and have async wrapper in LocalDataDictLoader for the sync version. As suggested by Stephen Toub here, that's not a recommended solution.
  2. Do as suggested by Josh's answer to the question here, in his IIMViewModelDL example, such that the interface will have a void method with callback parameter. While that solution will hide implementation details from client code, it feels to me like it has asynchronous invocation semantics, using a callback. In my opinion it's equivalent to always returning a Task<DataDict> and have the synchronous implementation do the loading synchronously then retuning a completed task with the result using TaskCompletionSource<DataDict>. This is wrong as option 1, for same reasons.
  3. Split the interface to two interfaces: IDataDictLoader for synchronous, IAsyncDataDictLoader : IDataDictLoader for asynchronous and synchronous. This solution provides client code with knowledge if the operation may be performed asynchronously or not, it doesn't "lie" about its nature. The downside is now the client code, which called CreateLoader(string configValue), will now have to use as/is conditional coding style to know it recieved an IDataDictLoader which is actually an IAsyncDataDictLoader or not.

So, what would you do to balance between demands on loose coupling and extensibility, while exposing true nature of whether the configured implementation supports asynchronous or not?

Community
  • 1
  • 1
Eldar S
  • 579
  • 3
  • 17

1 Answers1

0

I think what you should do is to make your interface asynchronous. If an implementation is synchronous, it can return an already-completed Task. This way, your consuming code doesn't need to know about the implementation and can treat it as if it was asynchronous.

svick
  • 236,525
  • 50
  • 385
  • 514
  • I mentioned this solution in my 2nd option. An implementation that returns a Task and yet runs synchronously is very confusing and somewhat unsafe. A code written in the UI may invoke this method on the UI thread, assuming that since it returns a Task its asynchronous and therefore will return fast, while in reality it may take minutes to return. I'll prefer to run the sync code in a Task and return it rather then running its synchronously and returning a completed Task, if at all. – Eldar S May 31 '13 at 18:31
  • @Eldar If the method takes long, that's another option, yeah. – svick May 31 '13 at 19:04
  • 1
    May I ask what you think about the option of separating the interfaces to a sync one and an async one extending it? – Eldar S Jun 01 '13 at 08:33