如果我不能在c#异步编程中使用TLS我可以使用什么?

时间:2015-08-11 23:49:56

标签: c# task-parallel-library

我目前的做法是在TLS中拥有一大堆上下文信息。

阅读有关执行上下文捕获的MSDN文章(http://blogs.msdn.com/b/pfxteam/archive/2012/06/15/executioncontext-vs-synchronizationcontext.aspx),似乎

a)我不能依赖于我在

上开始的同一个线程上继续运行

b)TLS未被克隆到延续的上下文中

那么我该怎么做(除了重新设计整个系统以不隐式地使用上下文绑定到执行路径)。我可以将自定义数据添加到将被捕获的执行上下文中吗?

我遇到了与TPL类似的问题,在这种情况下,我编写了自己的包装函数,将TLS克隆到TPL衍生的工作者中

2 个答案:

答案 0 :(得分:2)

如果您使用的是.NET 4.6,则可以使用AsyncLocal类。

答案 1 :(得分:1)

您可以使用逻辑调用上下文,它作为执行上下文的一部分流动:System.Runtime.Remoting.Messaging.CallContext.LogicalSetData / LogicalGetData

如果在TLS中存储任何可变数据,请注意逻辑调用上下文具有写时复制行为,并且可以分叉为多个执行路径(因此可以从多个线程同时访问,与使用TLS不同)。

如果存储对象,则会复制对它的引用(而不是深度克隆),因此可变数据可能是个问题。任何更改都不会传播到逻辑调用上下文的特定副本的范围之外。

如果您确实需要将逻辑调用上下文数据传播到外部调用者上下文,并且由于某种原因无法使用Task.Result,那么您仍然可以使用可变数据对象。在使用异步方法分支执行流程之前,您必须将其添加到之前的逻辑调用上下文中(或者使用Task.RunTask.Factory.StartNew之类的API输入新线程,Thread.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