如何获取当前任务参考?

时间:2011-07-14 22:39:08

标签: c# task-parallel-library

如何引用我的代码在其中执行的任务?

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

由于我正在调用一些接口方法,因此我不能将新创建的任务作为附加参数传递。

5 个答案:

答案 0 :(得分:6)

由于您无法更改界面或实施,因此您必须自行完成,例如使用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();
}

答案 1 :(得分:3)

这是一个" 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;
            };
        }
    }
}

答案 2 :(得分:1)

以下示例显示了如何实现,并通过@ stephen-cleary提供的答案解决了问题。这有点令人费解,但基本上关键在于下面的 TaskContext 类,它使用 CallContext.LogicalSetData CallContext.LogicalGetData 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);
            }
        }
    }
}

答案 3 :(得分:1)

如果可以使用.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));

答案 4 :(得分:0)

如果您可以更改界面(当我遇到类似问题时,这对我来说不是约束),对我来说,Lazy<Task>可以用来解决此问题。所以我尝试了。

它有效,至少对于我想要“当前任务”意味着什么。但这是微妙的代码,因为AsyncMethodThatYouWantToRun必须要做Task.Yield()

如果放弃,它将失败并显示System.AggregateException: 'One or more errors occurred. (ValueFactory attempted to access the Value property of this instance.)'

Lazy<Task> eventuallyATask = null; // silly errors about uninitialized variables :-/
eventuallyATask = new Lazy<Task>(
    () => AsyncMethodThatYouWantToRun(eventuallyATask));

Task t = eventuallyATask.Value; // actually start the task!

async Task AsyncMethodThatYouWantToRun(Lazy<Task> lazyThisTask)
{
    await Task.Yield(); // or else, the 'task' object won't finish being created!

    Task thisTask = lazyThisTask.Value;
    Console.WriteLine("you win! Your task got a reference to itself");
}

t.Wait();

或者,我们可以一路执行任务,并使用Task.Yield来解决问题,而不是TaskCompletionSource<Task>的微妙之处。 (消除所有潜在的错误/死锁,因为我们的任务会安全地释放线程,直到它自己知道为止!)

    var eventuallyTheTask = new TaskCompletionSource<Task>();
    Task t = AsyncMethodThatYouWantToRun(eventuallyTheTask.Task); // start the task!
    eventuallyTheTask.SetResult(t); //unblock the task and give it self-knowledge

    async Task AsyncMethodThatYouWantToRun(Task<Task> thisTaskAsync)
    {
        Task thisTask = await thisTaskAsync; // gets this task :)
        Console.WriteLine("you win! Your task got a reference to itself (== 't')");
    }

    t.Wait();