0

There's some code I can't change:

protected virtual void Method1() { }
private void Method2() { /* ... */ }

public void Method3()
{
    Method1();
    Method2();
}

And when Method3() is called, I have to call a method returning a Task in the override of Method1. However, after that call, I need to know if Method2 has been called yet.

protected override async void Method1()
{
    await MyOtherMethodAsync();
    bool method2HasBeenCalledYet = ??
    if (method2HasBeenCalledYet)
    {
        // ...
    }
    else
    {
        // ...
    }
}

The thing is, it depends on whether MyOtherMethodAsync is actually asynchronous (like with a Task.Run()) or not (with, for example, a Task.CompletedTask).

I had an idea, it works every time I tested it, but I don't know if there are cases when it wouldn't work (edit: there are) or if there are more elegant solutions (edit: there clearly are).

protected override async void Method1()
{
    bool method2HasBeenCalledYet = await IsAsync(MyOtherMethodAsync);
    if (method2HasBeenCalledYet)
    {
        // ...
    }
    else
    {
        // ...
    }
}

private async Task<bool> IsAsync(Func<Task> asyncAction)
{
    int beforeThreadId = Thread.CurrentThread.ManagedThreadId;
    await asyncAction().ConfigureAwait(false);
    int afterThreadId = Thread.CurrentThread.ManagedThreadId;
    return beforeThreadId != afterThreadId;
}

--

Edit with the solution given by @Crowcoder and @Servy:

protected override async void Method1()
{
    var task = MyOtherMethodAsync();
    if (!task.IsCompleted)
    {
        await task;
        // ...
    }
    else
    {
        // ...
    }
}
2
  • 1
    You can't rely on thread id to tell you. You might get a different thread, you might not. Task does have IsCompleted and Status which you might make use of. Commented Dec 14, 2021 at 18:12
  • @Crowcoder: I definitely am 100% stupid. Just watching the IsCompleted is clearly the way to go. Commented Dec 14, 2021 at 18:20

1 Answer 1

3

So in the general case, given an arbitrary delegate, the problem is unsolvable.

You could perhaps specifically design certain methods to allow this code to determine if they'll run synchronously or asynchronously, but any approach you attempt to take to determine if a method is asynchronous or synchronous can be made to be incorrect in some situation, even if those situations are fringe.

First off, it's worth pointing out that you're calling the method in order to determine if it's asynchronous or not. If you mean to do that, that's fine, but generally I wouldn't expect a method I called named IsAsync to actually run it.

For your specific solution, it relies on assuming that the method is not called from a thread pool thread, or that the thread pool thread that the continuation is called in just happens to be different. It might be different, but it just as easily might not be. It's also a way more expensive way of determining the answer than other (also imperfect) solutions.

Your code relies on the behavior that await results in a check to see if the task is completed and continues on normally if it is. You can just do that. Such a method would be much better than yours, but still not entirely accurate.

bool InaccurateIsAsync(Func<Task> func) => func().IsCompleted;

This method is way simpler, more efficient, and less inaccurate than your solution, but it can still have false negatives. If a method is actually asynchronous, but is really fast, it's possible for it to return an incomplete task, and then complete it before you check it. That's unlikely, so the above method may be good enough if you either know that if it is asynchronous it'll be long running, or that you're just okay with it being asynchronous if it completes so quickly. (Note that Task.Yield is specifically designed to do exactly this. It won't be complete when first checked, but it'll be complete immediately after, so a method returning that task, or one composed of it, could result in this behavior even if it was not constructed to maliciously trick it.)

Another reason this solution is inaccurate is it's entirely possible for a method to sometimes run synchronously and sometimes run asynchronously. Just because you ran it once and it returned a completed task doesn't mean it will the next time you call it. (So if you're calling IsAsync to see if future calls will be synchronous you need to know something about the specific implementation to know that it won't do that).

Sign up to request clarification or add additional context in comments.

1 Comment

I actually want to call the method, so, while not correctly named, the IsAsync method worked as intented, and I have no problem with the fact that a method can be sometimes async, sometimes not. But your solution to use the IsCompleted is perfect for my problem (I don't know why I didn't think of it before - I probably need sleep). Thanks a lot for your time.

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.