如果我不能在 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.RunTask.Factory.StartNewThread.StartThreadPool.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