如果我不能在 C# 异步编程中使用 TLS,我可以使用什么?
if I cant use TLS in c# async programming what can I use?
我目前的做法是在 TLS 中拥有一大堆上下文信息。
阅读有关执行上下文捕获的 MSDN 文章 (http://blogs.msdn.com/b/pfxteam/archive/2012/06/15/executioncontext-vs-synchronizationcontext.aspx) 似乎
a) 我不能依赖 运行 在我开始的同一个线程上的延续
b) TLS 未克隆到延续的上下文中
那么我能做什么(除了重新设计整个系统以不使用隐式绑定到执行路径的上下文)。我可以将自定义数据添加到将被捕获的执行上下文吗?
我在使用 TPL 时遇到了类似的问题,在那种情况下,我编写了自己的包装函数,将 TLS 克隆到 TPL 生成的工作程序中
如果您使用的是 .NET 4.6,则可以使用 AsyncLocal class.
您可以为此使用逻辑调用上下文,它作为执行上下文的一部分流动:System.Runtime.Remoting.Messaging.CallContext.LogicalSetData
/LogicalGetData
。
如果您在 TLS 中存储任何可变数据,请注意逻辑调用上下文具有写时复制行为,并且可以分叉到多个执行路径(因此可以从多个线程同时访问,这与 TLS 不同) .
如果您存储对象,则会复制对它的引用(不是深度克隆),因此可变数据可能是个问题。任何更改都不会传播到逻辑调用上下文的特定副本的范围之外。
如果您确实需要将逻辑调用上下文数据传播到外部调用者上下文,并且出于某种原因您不能使用 Task.Result
,您仍然可以使用可变数据对象。在使用异步方法分支执行流程之前,您必须将其添加到逻辑调用上下文 before(或使用 API 像 Task.Run
, Task.Factory.StartNew
、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
。
我目前的做法是在 TLS 中拥有一大堆上下文信息。
阅读有关执行上下文捕获的 MSDN 文章 (http://blogs.msdn.com/b/pfxteam/archive/2012/06/15/executioncontext-vs-synchronizationcontext.aspx) 似乎
a) 我不能依赖 运行 在我开始的同一个线程上的延续
b) TLS 未克隆到延续的上下文中
那么我能做什么(除了重新设计整个系统以不使用隐式绑定到执行路径的上下文)。我可以将自定义数据添加到将被捕获的执行上下文吗?
我在使用 TPL 时遇到了类似的问题,在那种情况下,我编写了自己的包装函数,将 TLS 克隆到 TPL 生成的工作程序中
如果您使用的是 .NET 4.6,则可以使用 AsyncLocal class.
您可以为此使用逻辑调用上下文,它作为执行上下文的一部分流动:System.Runtime.Remoting.Messaging.CallContext.LogicalSetData
/LogicalGetData
。
如果您在 TLS 中存储任何可变数据,请注意逻辑调用上下文具有写时复制行为,并且可以分叉到多个执行路径(因此可以从多个线程同时访问,这与 TLS 不同) .
如果您存储对象,则会复制对它的引用(不是深度克隆),因此可变数据可能是个问题。任何更改都不会传播到逻辑调用上下文的特定副本的范围之外。
如果您确实需要将逻辑调用上下文数据传播到外部调用者上下文,并且出于某种原因您不能使用 Task.Result
,您仍然可以使用可变数据对象。在使用异步方法分支执行流程之前,您必须将其添加到逻辑调用上下文 before(或使用 API 像 Task.Run
, Task.Factory.StartNew
、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
。