0

I am trying to determine whether a particular method was called by an async method.

This answer (which was, granted, describing a somewhat different set of circumstances) suggested using CallerMemberName attribute to find the name of the calling method. Indeed, the signature of my method looks like this:

public void LogCallAsync([CallerMemberName] string caller = "", params object[] parameters)

which works great if you're doing something like

logger.LogCallAsync();

It would also work great if you have a fixed number of parameters. However, given that the next parameter is of type params object[], obviously that isn't the case, so if you try to do something like

logger.LogCallAsync(someObject, someOtherObject)

I'll get a compile exception because someObject isn't a string. I tried the following as a workaround:

logger.LogCallAsync(nameof(CurrentMethod), someObject, someOtherObject);

which is pretty ugly. Actually, I'd prefer it if I didn't have to do that, so if anyone has any suggestions in that regard that would be great, but on to my main question: is there some way of preventing people from calling this improperly? In particular, I'd like to know if "CurrentMethod" is, in fact, an actual async method.

If I look at an instance of StackTrace, for example, is there a reliable way of telling based on that? This article (if I'm reading it correctly) seems to imply that my current solution (see my code sample below) is correct, but it's not really an "authoritative" source and it's about 5 years old at this point.

Let me show a code sample to illustrate how I'm trying to solve this right now:

private static void GetCaller()
    {
        StackTrace stack = new StackTrace();
        MethodBase method = stack.GetFrame(1).GetMethod();

        Trace.TraceInformation("Method name: " + method.Name);
    }

    // Some arbitrary async method
    private static async Task UseReflection()
    {
        // Do some kind of work
        await Task.Delay(100);

        // Figure out who called this method in the first place
        // I want some way of figuring out that the UseReflection method is async
        GetCaller();
    }

    static void Main(string[] args)
    {
        // AsyncPump is basically to make Async work more like it does on a UI application
        // See this link: https://blogs.msdn.microsoft.com/pfxteam/2012/01/20/await-synchronizationcontext-and-console-apps/
        AsyncPump.Run(async () =>
        {
            // In this case, it identifies the calling method as "MoveNext"
            // Question: in cases like this, will this always be the case (i.e. will it always be MoveNext)?
            await UseReflection();
        });

        // In this case, it identifies the calling method as "Main"
        GetCaller();
    }

I'm using Visual Studio 2015 and .NET 4.6 for what it's worth. My question, then, is: can I guarantee that code will always work in a way that's similar to what I have above? For example, if GetCaller was called by an async method, will I always get MoveNext off the stack trace? Also, does anyone know if Microsoft documents that somewhere (in case I'm asked to prove that my solution will work)?

EDIT: The primary purpose here is to log the name and parameters of the method that called the logger. If the caller is not async, then I know that the following code

StackTrace stack = new StackTrace();
MethodBase method = stack.GetFrame(1).GetMethod();

will give me information on who the caller is. However, that obviously won't work at all if the caller is async. In that case I currently do the following:

StackTrace st = new StackTrace();
StackFrame callFrame = st.GetFrames().ToList().FirstOrDefault(x => x.GetMethod().Name == caller);

where caller is the name of the method call that I passed, like in the signature I listed above:

public void LogCallAsync([CallerMemberName] string caller = "", params object[] parameters)

Obviously, this is less efficient. I could, in theory, just do this for every logger call, but it would be a little bit of a performance hit and I'd rather avoid that if possible.

Community
  • 1
  • 1
  • 3
    Your question has [XY Problem](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) written all over it. I find it difficult to believe that it is useful in any way to know whether your caller is in fact a method declared as `async`, never mind possible. Methods can look like an `async`-declared method without actually being one, and other methods may behave similarly to `async` methods (i.e. in a way you might actually care about) without looking like one. What problem is it you have that you actually want to solve? – Peter Duniho Nov 16 '16 at 23:54
  • @PeterDuniho It could be an XY Problem case, if I'm taking the wrong approach that would definitely be good to know too. I'll edit to include more details shortly. – EJoshuaS - Stand with Ukraine Nov 16 '16 at 23:57
  • @PeterDuniho I edited - does that clarify this? – EJoshuaS - Stand with Ukraine Nov 17 '16 at 00:07
  • Sorry, not really. I don't understand your `FirstOrDefault()` implementation...what is `caller` in that example? If you already have `caller`, is it that you're not trying to get the method name in that case, but just the stack frame with the arguments? If so, how does that example help you get the arguments passed to the `async` method, given that they are no longer on the stack once the originally called method executes an `await` expression? And even if it were, how are you getting the arguments? (Note that calling `ToList()` is unnecessary in any case.) – Peter Duniho Nov 17 '16 at 01:51
  • 1
    Please provide a [mcve] with some examples that show completely what you're trying to do, including different calls along with a precise description of what output you want to get and what the code outputs instead. Note that there's no reason to introduce the `AsyncPump` class here; you can just call `Wait()` on the returned `Task`, for the purposes of the example (even though one would likely prefer not to do that in production code). – Peter Duniho Nov 17 '16 at 01:57
  • @PeterDuniho All I'm trying to do is log what method call took place and what parameters it was passed. – EJoshuaS - Stand with Ukraine Nov 17 '16 at 02:50
  • Maybe you should start by showing how you expect to do that for methods that _aren't_ `async` method. In particular, it is not clear from the code you've posted so far how the method argument information is being included. – Peter Duniho Nov 17 '16 at 04:51
  • 1
    @EJoshuaS: You may find my [async diagnostics](https://github.com/StephenCleary/AsyncDiagnostics) package useful. It doesn't depend on any undocumented behavior. – Stephen Cleary Nov 17 '16 at 18:48

1 Answers1

2

Yes, all async methods end up in MoveNext.

SledgeHammer
  • 7,338
  • 6
  • 41
  • 86
  • So if I do something like what I'm doing in my sample it'll definitely work how I think it does? Do you know if Microsoft documents that somewhere (in case my boss asks me to prove that it'll work)? – EJoshuaS - Stand with Ukraine Nov 16 '16 at 23:34
  • 2
    @EJoshuaS, I don't think its "officially" documented since its a behind the scenes thing, but this blog post should get your boss off your back :)... http://apmblog.dynatrace.com/2014/10/09/behind-net-4-5-async-scene-performance-impact-asynchronous-programming-c/ ... it explains how the compiler generates async code. – SledgeHammer Nov 16 '16 at 23:38
  • 3
    @EJoshuaS Note that you can also be inside a method named `MoveNext` if you explicitly name a method that, or you're writing a generator. For example: `IEnumerable TestIt() { yield return 1; yield return 2; }`. Not enough to decide it's an async method. *Why* does it matter if the caller is async or not? – Rob Nov 16 '16 at 23:48
  • 1
    @EJoshuaS if you want to bullet proof against the case Rob mentioned, you can also look for System.Runtime.CompilerServices.TaskAwaiter in the call stack. – SledgeHammer Nov 16 '16 at 23:56