我目前的做法是在TLS中拥有一大堆上下文信息。
阅读有关执行上下文捕获的MSDN文章(http://blogs.msdn.com/b/pfxteam/archive/2012/06/15/executioncontext-vs-synchronizationcontext.aspx),似乎
a)我不能依赖于我在
上开始的同一个线程上继续运行b)TLS未被克隆到延续的上下文中
那么我该怎么做(除了重新设计整个系统以不隐式地使用上下文绑定到执行路径)。我可以将自定义数据添加到将被捕获的执行上下文中吗?
我遇到了与TPL类似的问题,在这种情况下,我编写了自己的包装函数,将TLS克隆到TPL衍生的工作者中
答案 0 :(得分:2)
如果您使用的是.NET 4.6,则可以使用AsyncLocal类。
答案 1 :(得分:1)
您可以使用逻辑调用上下文,它作为执行上下文的一部分流动:System.Runtime.Remoting.Messaging.CallContext.LogicalSetData
/ LogicalGetData
。
如果在TLS中存储任何可变数据,请注意逻辑调用上下文具有写时复制行为,并且可以分叉为多个执行路径(因此可以从多个线程同时访问,与使用TLS不同)。
如果存储对象,则会复制对它的引用(而不是深度克隆),因此可变数据可能是个问题。任何更改都不会传播到逻辑调用上下文的特定副本的范围之外。
如果您确实需要将逻辑调用上下文数据传播到外部调用者上下文,并且由于某种原因无法使用Task.Result
,那么您仍然可以使用可变数据对象。在使用异步方法分支执行流程之前,您必须将其添加到之前的逻辑调用上下文中(或者使用Task.Run
,Task.Factory.StartNew
之类的API输入新线程,Thread.Start
,ThreadPool.QueueUserWorkItem
等)。需要注意的是:现在多个线程可能会竞争同时改变该值。
通过示例很容易显示:
using System;
using System.Runtime.Remoting.Messaging;
using System.Threading.Tasks;
namespace ConsoleApplication
{
class Program
{
static async Task TestAsync(string id, int delay)
{
// but we might not even have any asynchrony here
// but making the method "async" is already enough
// for the copy-on-write behavior to trigger
await Task.Delay(delay).ConfigureAwait(false);
// copy on write here
CallContext.LogicalSetData("name1", "value1-modified-by-" + id);
var mutableData = (MutableData<string>)CallContext.LogicalGetData("name2");
Console.WriteLine(CallContext.LogicalGetData("name1"));
// racing to set mutableData.Data
mutableData.Data = "value2-modified-by-" + id;
Console.WriteLine(mutableData.Data);
}
static void Main(string[] args)
{
CallContext.LogicalSetData("name1", "value1");
var mutableData = new MutableData<string> { Data = "value2" };
CallContext.LogicalSetData("name2", mutableData);
Console.WriteLine(CallContext.LogicalGetData("name1"));
Task.WaitAll(TestAsync("A", 1000), TestAsync("B", 1000));
Console.WriteLine(CallContext.LogicalGetData("name1"));
Console.WriteLine(mutableData.Data);
Console.ReadLine();
}
}
class MutableData<T>
{
readonly object _lock = new Object();
T _data = default(T);
public T Data
{
get
{
lock (_lock)
{
return _data;
}
}
set
{
lock (_lock)
{
_data = value;
}
}
}
}
}
查看Stephen Cleary的"Implicit Async Contex"了解更多详情。
我相信相同的传播行为也适用于.NET 4.6 AsyncLocal
,即使它内部没有使用CallContext
。