Как получить текущую ссылку на задачу? - PullRequest
14 голосов
/ 15 июля 2011

Как я могу получить ссылку на задачу, внутри которой выполняется мой код?

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

Поскольку я вызываю некоторый интерфейсный метод, я не могу просто передать вновь созданную задачу в качестве дополнительного параметра.

Ответы [ 4 ]

7 голосов
/ 15 июля 2011

Поскольку вы не можете изменить ни интерфейс, ни реализацию, вам придется сделать это самостоятельно, например, используя 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();
}
3 голосов
/ 27 октября 2015

Вот класс "hacky", который можно использовать для этого.
Просто используйте свойство 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;
            };
        }
    }
}
1 голос
/ 29 июня 2018

Если вы можете использовать .NET 4.6 или более позднюю версию, .NET Standard или .NET Core, они решили эту проблему с помощью AsyncLocal. https://docs.microsoft.com/en-gb/dotnet/api/system.threading.asynclocal-1?view=netframework-4.7.1

Если нет, вам необходимо настроить хранилище данных до его использования и получить к нему доступ через замыкание, а не поток или задачу. ConcurrentDictionary поможет скрыть любые ошибки, которые вы совершаете, делая это.

Когда код ожидает, текущая задача освобождает поток - то есть потоки не связаны с задачами, по крайней мере, в модели программирования.

Демо-версия:

// I feel like demo code about threading needs to guarantee
// it actually has some in the first place :)
// The second number is IOCompletionPorts which would be relevant
// if we were using IO (strangely enough).
var threads = Environment.ProcessorCount * 4;
ThreadPool.SetMaxThreads(threads, threads);
ThreadPool.SetMinThreads(threads, threads);

var rand = new Random(DateTime.Now.Millisecond);

var tasks = Enumerable.Range(0, 50)
    .Select(_ =>
    {
        // State store tied to task by being created in the same closure.
        var taskState = new ConcurrentDictionary<string, object>();
        // There is absolutely no need for this to be a thread-safe
        // data structure in this instance but given the copy-pasta,
        // I thought I'd save people some trouble.

        return Task.Run(async () =>
        {
            taskState["ThreadId"] = Thread.CurrentThread.ManagedThreadId;
            await Task.Delay(rand.Next() % 100);
            return Thread.CurrentThread.ManagedThreadId == (int)taskState["ThreadId"];
        });
    })
    .ToArray();

Task.WaitAll(tasks);
Console.WriteLine("Tasks that stayed on the same thread: " + tasks.Count(t => t.Result));
Console.WriteLine("Tasks that didn't stay on the same thread: " + tasks.Count(t => !t.Result));
1 голос
/ 08 сентября 2015

В следующем примере показано, как этого можно добиться, решив проблему с помощью ответа, предоставленного @ stephen-cleary. Это немного запутанно, но по сути ключ находится в классе TaskContext ниже, который использует CallContext.LogicalSetData , CallContext.LogicalGetData и CallContext.FreeNamedDataSlot , которые полезны для создания ваших собственных контекстов задач. Остальная часть пуха - ответить на вопрос ОП:

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