根据我使用的是基于异步/等待的代码还是基于tpl的代码,我得到了关于清理逻辑CallContext的两种不同行为。
如果使用以下异步/等待代码,我可以完全按照预期设置和清除逻辑CallContext

class Program
{
    static async Task DoSomething()
    {
        CallContext.LogicalSetData("hello", "world");

        await Task.Run(() =>
            Debug.WriteLine(new
            {
                Place = "Task.Run",
                Id = Thread.CurrentThread.ManagedThreadId,
                Msg = CallContext.LogicalGetData("hello")
            }))
            .ContinueWith((t) =>
                CallContext.FreeNamedDataSlot("hello")
                );

        return;
    }

    static void Main(string[] args)
    {
        DoSomething().Wait();

        Debug.WriteLine(new
        {
            Place = "Main",
            Id = Thread.CurrentThread.ManagedThreadId,
            Msg = CallContext.LogicalGetData("hello")
        });

    }
}

上述输出如下:
{place=task.run,id=9,msg=world}
{place=main,id=8,msg=}
注意Msg =这表示主线程上的CallContext已被释放且为空。
但当我切换到纯tpl/tap代码时,我无法达到相同的效果……
class Program
{
    static Task DoSomething()
    {
        CallContext.LogicalSetData("hello", "world");

        var result = Task.Run(() =>
            Debug.WriteLine(new
            {
                Place = "Task.Run",
                Id = Thread.CurrentThread.ManagedThreadId,
                Msg = CallContext.LogicalGetData("hello")
            }))
            .ContinueWith((t) =>
                CallContext.FreeNamedDataSlot("hello")
                );

        return result;
    }

    static void Main(string[] args)
    {
        DoSomething().Wait();

        Debug.WriteLine(new
        {
            Place = "Main",
            Id = Thread.CurrentThread.ManagedThreadId,
            Msg = CallContext.LogicalGetData("hello")
        });
    }
}

上述输出如下:
{place=task.run,id=10,msg=world}
{place=main,id=9,msg=world}
有什么我可以做的来强制tpl“释放”逻辑CallContext的方法与异步/等待代码相同吗?
我对CallContext的替代品不感兴趣。
我希望修复上面的tpl/tap代码,以便在针对.net 4.0框架的项目中使用它。如果这在.net 4.0中是不可能的,我仍然很好奇是否可以在.net4.5中实现。

最佳答案

async方法中,写入时复制CallContext
当异步方法启动时,它会通知其逻辑调用上下文以激活写时复制行为。这意味着当前逻辑调用上下文实际上没有更改,但它被标记为如果代码确实调用了CallContext.LogicalSetData,则在更改之前,逻辑调用上下文数据将被复制到新的当前逻辑调用上下文中。
Implicit Async Context ("AsyncLocal")
这意味着在您的async版本中,CallContext.FreeNamedDataSlot("hello")continuation是多余的,即使没有它:

static async Task DoSomething()
{
    CallContext.LogicalSetData("hello", "world");

    await Task.Run(() =>
        Console.WriteLine(new
        {
            Place = "Task.Run",
            Id = Thread.CurrentThread.ManagedThreadId,
            Msg = CallContext.LogicalGetData("hello")
        }));
}

CallContext中的Main不包含"hello"槽:
{place=task.run,id=3,msg=world}
{place=main,id=1,msg=}
在TPL等效中,在Task.Run之外的所有代码(应该是Task.Factory.StartNew as Task.Run >添加在.NET 4.5中)在同一线程上运行,具有相同的精确CallContext。如果要清除它,则需要在该上下文中(而不是在续文中)执行此操作:
static Task DoSomething()
{
    CallContext.LogicalSetData("hello", "world");

    var result = Task.Factory.StartNew(() =>
        Debug.WriteLine(new
        {
            Place = "Task.Run",
            Id = Thread.CurrentThread.ManagedThreadId,
            Msg = CallContext.LogicalGetData("hello")
        }));

    CallContext.FreeNamedDataSlot("hello");
    return result;
}

你甚至可以抽象出一个范围,以确保你总是清理自己之后:
static Task DoSomething()
{
    using (CallContextScope.Start("hello", "world"))
    {
        return Task.Factory.StartNew(() =>
            Debug.WriteLine(new
            {
                Place = "Task.Run",
                Id = Thread.CurrentThread.ManagedThreadId,
                Msg = CallContext.LogicalGetData("hello")
            }));
    }
}

使用:
public static class CallContextScope
{
    public static IDisposable Start(string name, object data)
    {
        CallContext.LogicalSetData(name, data);
        return new Cleaner(name);
    }

    private class Cleaner : IDisposable
    {
        private readonly string _name;
        private bool _isDisposed;

        public Cleaner(string name)
        {
            _name = name;
        }

        public void Dispose()
        {
            if (_isDisposed)
            {
                return;
            }

            CallContext.FreeNamedDataSlot(_name);
            _isDisposed = true;
        }
    }
}

关于c# - 在TPL中清理CallContext,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/29001266/

10-11 01:25