1

I'm consuming an "API" (I use the term loosely) that's implemented as a COM+ object. As far as I'm aware, there's no support for any of the queuing functionality COM+ offers, but I know pretty much exactly nil about COM+. Take it with a grain of salt. I have just one method available to me: invoke, and it's sync.

In a stroke of somewhat personal brilliance, I created a Web Api wrapper around this COM+ object, which has just one endpoint, invoking the COM+ object with the post body of the request. This has allowed me to make asynchronous calls to the Web Api instead (as well as remove the god-awful dependency from my apps).

Now, I'm creating a library to basically abstract all this mess, but I decided to implement a provider pattern where you can choose to either use the COM+ directly or the Web Api wrapper. I have an interface, obviously, with both sync and async methods. The Web Api provider is of course no problem as you can do an HTTP request either way, but I'm hitting a wall with the COM+ provider. Since async is not an option there.

So, I have three options as I see it:

  1. "Implement" the async interface method with a NotImplementedException, which is a violation of the interface segregation principle.

  2. Do something ill-advised like use Task.Run, which I know is not really async in this context, and basically lies to anyone programming against my library.

  3. Do something like create a sync and async version of the interface, which is better from a interface segregation standpoint, but worse from a provider pattern standpoint. If I depend on the async interface, then the COM+ provider can never be used, and if I depend on the sync interface, then I can never use the async methods of the Web Api provider.

Long and short, my general question is what do you do in a situation where you need to run something asynchronously, but the method you need to utilize can only be run synchronously? If there's no real alternative, then what is the least offensive way to satisfy an interface requirement?

EDIT

I forgot to mention one more option:

  1. Call the method sync inside the async implementation and then simply return Task.FromResult with the result. This means I'm not pulling a new thread (which could be an issue in a web application), but I'm not sure if this is really any better than Task.Run in the sense of lying to the consumer about this actually being "async".
Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • I don' see reasonable choices here except 2. If you really can't change the COM side, you don't have to "lie", it's a fact that doesn't depend on you, you do your best to wrap behavior underneath. If you can change it: https://msdn.microsoft.com/en-us/library/windows/desktop/ms692623.aspx – Simon Mourier Oct 04 '17 at 06:34
  • Yeah, nope, can't change it. Unfortunately, it's a third-party DLL provided by our POS (which very much means both point-of-sale and the other thing) provider, as an "API". It's been a constant source of migraines. – Chris Pratt Oct 04 '17 at 06:41
  • Task.Run() is pretty async. But COM has the feature that an object can tell the OS that it is not thread-safe. Tends to be useful, better than it randomly crashing once a day in a completely undiagnosable way in code you don't have the source of. You really do have to fret about thread-safety when you have hard evidence that a library is not thread-safe, such libraries never are. Just give it a [safe home](https://stackoverflow.com/a/21684059/17034). – Hans Passant Oct 04 '17 at 07:10

1 Answers1

1

The solution I landed on was to use Task.FromResult to return a Task even though nothing async is being done. While this is not actually async, it satisfies the interface. I then commented the method to note that it will run sync and should be passed as a delegate to Task.Run in situations where the thread cannot be blocked (GUIs, for example). Using Task.Run is not appropriate in every situation (web applications, for one) and is an implementation detail that should be decided upon by the consumer of the library.

That said, to truly implement this, you need to handle three Task scenarios: completed, faulted, and cancelled. Here's the actual code to do all this:

public Task<int> DoSomethingAsync(CancellationToken cancellationToken)  
{
    if (cancellationToken.IsCancellationRequested)
    {
        return Task.FromCanceled<int>(cancellationToken);
    }

    try
    {
        return Task.FromResult<int>(DoSomething());
    }
    catch (Exception e)
    {
        return Task.FromException<int>(e);
    }
}

First, this ensures that the operation hasn't been canceled. If it has, a canceled task is returned. Then, the sync work we need to do is wrapped in a try..catch block. If an exception is thrown, we'll need to return a faulted task, which includes that exception. Finally, if it completes correctly, we return a completed task.

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444