Как получить текущую ссылку на задание?
Как мне получить ссылку на задачу, в которой выполняется мой код?
ISomeInterface impl = new SomeImplementation();
Task.Factory.StartNew(() => impl.MethodFromSomeInterface(), new MyState());
...
void MethodFromSomeInterface()
{
Task currentTask = Task.GetCurrentTask(); // No such method?
MyState state = (MyState) currentTask.AsyncState();
}
Поскольку я вызываю некоторый метод интерфейса, я не могу просто передать вновь созданную задачу в качестве дополнительного параметра.
Ответы
Ответ 1
Поскольку вы не можете изменить интерфейс или реализацию, вам придется сделать это самостоятельно, например, используя ThreadStaticAttribute
:
static class SomeInterfaceTask
{
[ThreadStatic]
static Task Current { get; set; }
}
...
ISomeInterface impl = new SomeImplementation();
Task task = null;
task = Task.Factory.StartNew(() =>
{
SomeInterfaceTask.Current = task;
impl.MethodFromSomeInterface();
}, new MyState());
...
void MethodFromSomeInterface()
{
Task currentTask = SomeInterfaceTask.Current;
MyState state = (MyState) currentTask.AsyncState();
}
Ответ 2
Вот "хакерский" класс, который можно использовать для этого.
Просто используйте свойство CurrentTask, чтобы получить текущую текущую задачу.
Я настоятельно рекомендую использовать его где-нибудь рядом с производственным кодом!
public static class TaskGetter
{
private static string _propertyName;
private static Type _taskType;
private static PropertyInfo _property;
private static Func<Task> _getter;
static TaskGetter()
{
_taskType = typeof(Task);
_propertyName = "InternalCurrent";
SetupGetter();
}
public static void SetPropertyName(string newName)
{
_propertyName = newName;
SetupGetter();
}
public static Task CurrentTask
{
get
{
return _getter();
}
}
private static void SetupGetter()
{
_getter = () => null;
_property = _taskType.GetProperties(BindingFlags.Static | BindingFlags.NonPublic).Where(p => p.Name == _propertyName).FirstOrDefault();
if (_property != null)
{
_getter = () =>
{
var val = _property.GetValue(null);
return val == null ? null : (Task)val;
};
}
}
}
Ответ 3
В следующем примере показано, как это можно сделать, разрешив проблему с ответом, предоставленным @stephen-cleary. Это немного запутанно, но по существу ключ находится в классе TaskContext, который использует CallContext.LogicalSetDatastrong > , CallContext.LogicalGetDatastrong > и CallContext.FreeNamedDataSlot, которые полезны для создания собственных контекстов задач. Остальная часть пуха должна ответить на вопрос OP:
class Program
{
static void Main(string[] args)
{
var t1 = Task.Factory.StartNewWithContext(async () => { await DoSomething(); });
var t2 = Task.Factory.StartNewWithContext(async () => { await DoSomething(); });
Task.WaitAll(t1, t2);
}
private static async Task DoSomething()
{
var id1 = TaskContext.Current.Task.Id;
Console.WriteLine(id1);
await Task.Delay(1000);
var id2 = TaskContext.Current.Task.Id;
Console.WriteLine(id2);
Console.WriteLine(id1 == id2);
}
}
public static class TaskFactoryExtensions
{
public static Task StartNewWithContext(this TaskFactory factory, Action action)
{
Task task = null;
task = new Task(() =>
{
Debug.Assert(TaskContext.Current == null);
TaskContext.Current = new TaskContext(task);
try
{
action();
}
finally
{
TaskContext.Current = null;
}
});
task.Start();
return task;
}
public static Task StartNewWithContext(this TaskFactory factory, Func<Task> action)
{
Task<Task> task = null;
task = new Task<Task>(async () =>
{
Debug.Assert(TaskContext.Current == null);
TaskContext.Current = new TaskContext(task);
try
{
await action();
}
finally
{
TaskContext.Current = null;
}
});
task.Start();
return task.Unwrap();
}
}
public sealed class TaskContext
{
// Use your own unique key for better performance
private static readonly string contextKey = Guid.NewGuid().ToString();
public TaskContext(Task task)
{
this.Task = task;
}
public Task Task { get; private set; }
public static TaskContext Current
{
get { return (TaskContext)CallContext.LogicalGetData(contextKey); }
internal set
{
if (value == null)
{
CallContext.FreeNamedDataSlot(contextKey);
}
else
{
CallContext.LogicalSetData(contextKey, value);
}
}
}
}