2

I think I know the answer to this, but it's particular enough that I don't want to go telling other people stuff until I'm 100% certain.

Suppose I have a class with some dependency:

public interface IDependency
{
    int DoSomething(string value);
}

public class DependsOnSomething
{
    private readonly IDependency _dependency;

    public DependsOnSomething(IDependency dependency)
    {
        _dependency = dependency;
    }

    public int GetSomeValue(string input)
    {
        return _dependency.DoSomething(input);
    }
}

IDependency is an abstraction. I haven't yet determined what its implementation will be. I don't know whether it will be CPU-bound or perhaps make some API call.

What's more, the implemenation of IDependency could have its own dependencies, and the same could be true of those. They may or may not call async methods.

Would it be correct to say that

  • If I consider it likely that something, somewhere will be async, that I should make all of these methods async?
  • If nothing in any of the dependencies is async but at some point that changes, and I want to take advantage of that opportunity to free up a thread instead of letting it wait, I would need to go back through all of my methods and make everything async?

Generally I can plan for what does or doesn't need to be asynchronous, but I'm trying to understand the potential cost of a) guessing synchronous and b) guessing wrong.

Is my understanding of this correct?

One workaround to the problem might be, in some cases, to define both synchronous and asynchronous methods on interfaces. But that feels wrong because then the interface is describing implementation details, and if the underlying implementation isn't really asynchronous then my interface is lying. (And it could lead to me or someone else writing even more async methods to call something that isn't really async.)

  • Why can you not just figure out more of what IDependency is going to do and how it's going to work before building the class using it? – whatsisname May 3 at 18:05
  • 1
    The ambiguity of IDependency is deliberate. And, since it's an abstraction, I'd rather not have to decide how the implementation is going to work before I create the interface. Making sure that I understand whether or not I must do that is actually at the very heart of my question. My understanding is that we just have to work around that the best we can, which is I suppose is okay since no one has found it to be huge concern. – Scott Hannen May 3 at 18:16
  • 1
    Sync or async is not an implementation detail. It is a fundamental design decision. You need to make that decision based on the information you have now (or gather more info first). The price of making the wrong decision is either an overcomplicated design or having to redesign later. I'd go for the latter. – D Drmmr May 3 at 18:43
  • 2
    @ScottHannen: it's virtually impossible to abstract something you know nothing about and end up with an abstraction that improves the situation. Don't be so anxious to just write off "implementation details" because the devil is in the details, and it's not uncommon to discover a little detail that ends up being a significant amount of work to resolve. – whatsisname May 3 at 19:02
  • 2
    Task is not an implementation details, but part of the abstraction contract. Do you want consumer of the abstraction to be able to consume it asynchronously? If yes - use Task if not - don't use it. – Fabio May 5 at 22:51
2

The problem, of course, is that in .NET it's "async all the way down," meaning that once you go down the async path, your entire call chain must be async.

So your decision must be needs-based.

How do you decide which methods to make async? A good rule of thumb is the 50ms rule. If it is likely that the method is going to take more than 50 milliseconds to execute, make it async. Microsoft applied this rule to their WinRT platform, and found that about 10 percent of the method calls needed to be async.

This is mostly a UI rule. If you're talking to a server, the metrics are a bit different.

Further Reading
Should I make a fast operation async if the method is already async?

  • Just to clarify, what I'm trying to confirm is whether the interface must return a Task and have its method awaited. And the answer - which is what I was already 90% sure of - is that it depends on the implementation of the interface. It seems a tiny bit odd to have to create the interface around the implementation. The implementation could change. It's a leak. It's a real scenario, but it's manageable (as evidenced by everyone managing it) and the 50ms rule of thumb helps. – Scott Hannen May 3 at 18:23
  • If by that you mean "async is not part of the interface contract," you're right. Returning a Task in the interface method definition compels the client to deal with the method in an asynchronous way (although not necessarily using the await keyword), but the reverse is not true: a method that returns Type T in an interface can still be made asynchronous in the implementation. – Robert Harvey May 3 at 18:29
  • In short, an interface cannot reliably dictate the contract of a method with respect to asynchrony. – Robert Harvey May 3 at 18:32
  • I'm marking this as the answer because the first sentence tells me what I need. As mentioned in other comments I think I added too much "noise" to my question. Thanks. – Scott Hannen May 5 at 23:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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