gpt4 book ai didi

c# - 清理 TPL 中的 CallContext

转载 作者:可可西里 更新时间:2023-11-01 09:03:01 24 4
gpt4 key购买 nike

根据我使用的是基于 async/await 的代码还是基于 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 中是不可能的,我仍然很好奇它是否可以在 .net 4.5 中完成。

最佳答案

async 方法中,CallContext 在写入时被复制:

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.

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

Main 中的 CallContext 不会包含 "hello" 槽:

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

在 TPL 等同于 Task.Run 之外的所有代码(应该是 Task.Factory.StartNew 作为 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/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com