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
{
// ...
}
}
CodePudding user response:
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).