在TPL清理CallContext中 [英] Cleaning up CallContext in TPL

查看:149
本文介绍了在TPL清理CallContext中的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

根据我是否使用异步/等待基于code或TPL基于code,我收到有关的逻辑清理两种不同的行为 CallContext中

Depending on whether I'm using async/await based code or TPL based code, I'm getting two different behaviors regarding the clean-up of logical CallContext.

我可以设置和清晰的逻辑 CallContext中正是我希望如果我用下面的异步/等待code:

I can set and clear logical CallContext exactly as I expect if I use the following async/await code:

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")
        });

    }
}

以上输出以下内容:

The above outputs the following:

{地方= Task.Run,​​ID = 9,味精=世界}结果
  {广场=主,ID = 8,味精=}

{ Place = Task.Run, Id = 9, Msg = world }
{ Place = Main, Id = 8, Msg = }

注意消息= 这表明 CallContext中在主线程中被释放,是空的。

Notice the Msg = which indicates that CallContext on the main thread has been freed and is empty.

但是,当我切换到纯TPL / TAP code,我不能达到同样的效果...

But when I switch to pure TPL / TAP code I can't achieve the same effect...

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")
        });
    }
}

以上输出以下内容:

The above outputs the following:

{地方= Task.Run,​​ID = 10,味精=世界}结果
  {广场=主,ID = 9,味精=世界}

{ Place = Task.Run, Id = 10, Msg = world }
{ Place = Main, Id = 9, Msg = world }

有什么我可以做要挟TPL自由的逻辑 CallContext中相同的方式,异步/的await code呢?

Is there anything I can do to coerce TPL to "free" the logical CallContext the same way as the async/await code does?

我不感兴趣的替代品 CallContext中

I am not interested in alternatives to CallContext.

我希望能得到上面的TPL / TAP code固定,这样我可以在面向.NET 4.0框架项目中使用。如果不是在.NET 4.0中可能的话,我还是好奇,如果它可以在.NET 4.5来完成。

I'm hoping to get the above TPL/TAP code fixed so that I can use it in projects targeting the .net 4.0 framework. If that is not possible in .net 4.0, I'm still curious if it can be done in .net 4.5.

推荐答案

异步方法 CallContext中是复制上写:

当异步方法启动时,它会通知其逻辑调用上下文激活写入时复制行为。这意味着当前的逻辑调用上下文实际上并没有改变,但它标记,以便如果你的code不叫 CallContext.LogicalSetData ,逻辑调用上下文数据复制进入一个新的当前逻辑调用上下文前被改变。

When an async method starts, it notifies its logical call context to activate copy-on-write behavior. This means the current logical call context is not actually changed, but it is marked so that if your code does call CallContext.LogicalSetData, the logical call context data is copied into a new current logical call context before it is changed.

隐异步上下文(AsyncLocal)

这意味着,在你的异步版本 CallContext.FreeNamedDataSlot(你好)延续冗余,因为即使没有它:

That means that in your async version the CallContext.FreeNamedDataSlot("hello") continuation is redundant as even without it:

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中将不包含你好槽:

{地方= Task.Run,​​ID = 3,消息=世界}结果
  {广场=主,ID = 1,消息=}

{ Place = Task.Run, Id = 3, Msg = world }
{ Place = Main, Id = 1, Msg = }

在TPL相当于外 Task.Run (这应该是 Task.Factory.StartNew Task.Run 在.NET 4.5中添加)与运行在完全相同 CallContext中 。如果你想清理它,你需要做的,关于这方面(而不是在延续):

In the TPL equivalent all code outside the Task.Run (which should be Task.Factory.StartNew as Task.Run was added in .Net 4.5) runs on the same thread with the same exact CallContext. If you want to clean it you need to do that on that context (and not in the continuation):

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;
}

您甚至可以抽象一个范围出它确保你总是好自己清理:

You can even abstract a scope out of it to make sure you always clean up after yourself:

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;
        }
    }
}

这篇关于在TPL清理CallContext中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆