Getting the method name of a task
Asked Answered
O

4

4

I am looking to get the method/action name from a task in C#. Specifically I am implementing a custom task scheduler, and would like to generate statistics on the duration a task runs, which I will then aggregate by the method running inside of the task. In the visual studio debugger you can access this and see the m_action private variable, as well as the debugger display annotation, displays it as Method={0}. Is there any way to get access to this from the Task itself?

Olimpiaolin answered 23/8, 2012 at 5:38 Comment(2)
The question is, how useful that would be. The lambda expression you (typically) pass to a Task are turned into methods by the C# compiler and get compiler-generated names. Those names are not very telling and it'll be hard to re-associate them with the actual code.Reduce
@Reduce If were okay with limiting yourself only to very simple lambdas, you could use Expression instead of a delegate and get the method name from that. (Of course, an Expression can invoke several methods, so you would have to somehow choose which one to show. And it also may not call any methods.)Whereinto
M
2

Well, you could use reflection to get at the private m_action field, given a Task variable task:

    var fieldInfo = typeof(Task).GetField("m_action", BindingFlags.Instance | BindingFlags.NonPublic);
    Delegate action = fieldInfo.GetValue(task) as Delegate;

Then get the Name of the method and the DeclaringType:

    var name = action.Method.Name;
    var type = action.Method.DeclaringType.FullName;

To get the fully qualified method (type + "." + name)...

But, as soon as the task executes to completion, m_action is null. I'm not sure how this would apply with TaskFactory.StartNew...

Mcghee answered 23/8, 2012 at 19:25 Comment(0)
S
3

You could inherit from Task to make this real easy... I'm just going to implement the first constructor here for the example:

public class NamedTask : Task {
    public string MethodName { get; set; }
    public NamedTask(Action action) : base(action) {
        MethodName = action.Method.Name;
    }
    public NamedTask(Action action, CancellationToken cancellationToken) : base(action, cancellationToken) {}
    public NamedTask(Action action, TaskCreationOptions creationOptions) : base(action, creationOptions) {}
    public NamedTask(Action action, CancellationToken cancellationToken, TaskCreationOptions creationOptions) : base(action, cancellationToken, creationOptions) {}
    public NamedTask(Action<object> action, object state) : base(action, state) {}
    public NamedTask(Action<object> action, object state, CancellationToken cancellationToken) : base(action, state, cancellationToken) {}
    public NamedTask(Action<object> action, object state, TaskCreationOptions creationOptions) : base(action, state, creationOptions) {}
    public NamedTask(Action<object> action, object state, CancellationToken cancellationToken, TaskCreationOptions creationOptions) : base(action, state, cancellationToken, creationOptions) {}
}

After that...

NamedTask task = new NamedTask(() => AsyncMethod(arg1, arg2, argN));
string methodName = task.MethodName; // there's the name!

More examples. Inherit from Task<T>:

public class NamedTask<T> : Task<T> {
    public string MethodName { get; set; }
    public NamedTask(Func<T> function) : base(function) {
        MethodName = function.Method.Name;
    }
    public NamedTask(Func<T> function, string methodName) : base(function) {
        MethodName = methodName;
    }
    ...
}

Handle anonymous methods:

NamedTask<bool> task2 = new NamedTask<bool>(() => {
                // some arbitrary code
                return true;
    });

NamedTask<bool> task3 = new NamedTask<bool>(() => {
                // some arbitrary code
                return true;
    }, "ReturnTrueMethod");

string methodName2 = task2.MethodName; // returns "<LongRunning_Async>b__19"
string methodName3 = task3.MethodName; // returns "ReturnTrueMethod"
Shamus answered 23/8, 2012 at 6:22 Comment(4)
Interesting idea. Except: 1. MethodInfo.ToString() doesn't return just the method, name, it returns the whole signature. E.g. something like Void AsyncMethod(). To get just the name, use the Name property. 2. As noted by Christian in a comment, this won't give useful results for lambdas.Whereinto
Good points. For #1, if you really want just the method name, you can parse it out of the signature. For #2, in my example, I used a lambda... so I think maybe you mean it won't give anything useful for anonymous methods? In this case, it will still return a method name - it will just be the compiler generated method name for the anonymous method. Here you could make another constructor that allowed you to specify your own name for the anonymous method when creating the task.Shamus
Like I said, you can use the Name property, no need to parse anything.Whereinto
Ah, action.Method.Name. Yes! Nice one. I will change the post to reflect this.Shamus
M
2

Well, you could use reflection to get at the private m_action field, given a Task variable task:

    var fieldInfo = typeof(Task).GetField("m_action", BindingFlags.Instance | BindingFlags.NonPublic);
    Delegate action = fieldInfo.GetValue(task) as Delegate;

Then get the Name of the method and the DeclaringType:

    var name = action.Method.Name;
    var type = action.Method.DeclaringType.FullName;

To get the fully qualified method (type + "." + name)...

But, as soon as the task executes to completion, m_action is null. I'm not sure how this would apply with TaskFactory.StartNew...

Mcghee answered 23/8, 2012 at 19:25 Comment(0)
C
0
  1. Try reflection to get m_action variable.
  2. Try getting info from Envorinment.StackTrace from inside the task or directly called methods by it.
Californium answered 23/8, 2012 at 6:1 Comment(0)
K
0

Here is the only thing that is working for me with async task in a background thread :

private static string MethodName(this Task task)
{
    const string start = "+<";
    const string end = ">d__";

    var fullName = task.GetType().FullName;
    if (string.IsNullOrEmpty(fullName))
       return string.Empty;

    var methodName = fullName.Substring(fullName.IndexOf(start) + start.Length);
    return methodName.Remove(methodName.IndexOf(end));
}

Hope it might help or give ideas.

Edit:

Actually, I was trying to get the method name of a task that I launch in background, a bit like fire but don't forget. And, what I've inderstood so far is that where I need the name of the task, all the previous answer didn't solve my case since the action.Method or task.Method is always null.

Here is an exemple of my use case :

_anyDBService.CleanStuffAsync(stuff).RunInBackground(_logger);

In that exemple, I just wanted the method name CleanStuffAsync. And I end up with the previous simple MethodeName helper function in my TaskExtensions file, here it is :

public static class TaskExtensions
{
    public static void RunInBackground(this Action action, ILogger logger) =>
        action.RunWorkInBackground(logger, action.MethodName());

    public static void RunInBackground(this Delegate fonc, ILogger logger) =>
        fonc.RunWorkInBackground(logger, fonc.MethodName().GetDefaultValue());

    public static void RunInBackground(this Task task, ILogger logger) =>
        task.RunInBackground(logger, task.MethodName());

    private static void RunInBackground(this Task task, ILogger logger, string methodeName) =>
        task.RunWorkInBackground(logger, methodeName);

    private static string MethodName(this Delegate fonc) =>
        fonc.Method.Name;

    private static string MethodName(this Action action) =>
        action.Method.Name;

    private static string MethodName(this Task task)
    {
        const string start = "+<";
        const string end = ">d__";

        var fullName = task.GetType().FullName;
        if (string.IsNullOrEmpty(fullName))
            return string.Empty;

        var methodName = fullName[(fullName.IndexOf(start) + start.Length)..];
        return methodName.Remove(methodName.IndexOf(end));
    }

    private static void RunWorkInBackground<T>(this T work, ILogger logger, string methodName)
    {
        const string errorMsgPrefix = "Unexpected error occured in ";
        try
        {
            ThreadPool.QueueUserWorkItem(async cancellationToken =>
            {
                try
                {
                    logger.LogInformation(string.Concat("Running task '", methodName, "' in background..."));
                    await Task.Run(() => work);
                }
                catch (Exception ex)
                {
                    logger.LogError(string.Concat(errorMsgPrefix, "'", methodName, "' :", Environment.NewLine, ex.Message, Environment.NewLine, ex.StackTrace));
                }
            });
        }
        catch (Exception ex)
        {
            logger.LogError(string.Concat(errorMsgPrefix, "'", nameof(RunWorkInBackground), "' :", Environment.NewLine, ex.Message));
            throw;
        }
    }
}
Kuchen answered 26/10, 2023 at 15:53 Comment(3)
No reflection, no casting, no boxing or unboxing...Kuchen
Welcome to Stack Overflow! Thank you for your answer. Please provide more details about your solution. Code snippets, high quality descriptions, or any relevant information would be great. Clear and concise answers are more helpful and easier to understand for everyone. Edit your answer with specifics to raise the quality of your answer. For more information: How To: Write good answers. Happy coding!Cristinecristiona
@AztecCodes, I've edited my answer to add information. Thank you for your advice.Kuchen

© 2022 - 2024 — McMap. All rights reserved.