如何引用我的代码在其中执行的任务?
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();
}
由于我正在调用一些接口方法,因此我不能将新创建的任务作为附加参数传递。
答案 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();