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?
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...
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"
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 Name
property, no need to parse anything. –
Whereinto 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...
- Try reflection to get m_action variable.
- Try getting info from
Envorinment.StackTrace
from inside the task or directly called methods by it.
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;
}
}
}
© 2022 - 2024 — McMap. All rights reserved.
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. – ReduceExpression
instead of a delegate and get the method name from that. (Of course, anExpression
can invoke several methods, so you would have to somehow choose which one to show. And it also may not call any methods.) – Whereinto