Superman
Superman

Reputation: 3784

Getting the method name of a task

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?

Upvotes: 4

Views: 6610

Answers (4)

Islem BEZZARGA
Islem BEZZARGA

Reputation: 59

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;
        }
    }
}

Upvotes: 0

Peter Ritchie
Peter Ritchie

Reputation: 35881

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...

Upvotes: 2

James
James

Reputation: 2841

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"

Upvotes: 3

AgentFire
AgentFire

Reputation: 9780

  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.

Upvotes: 0

Related Questions