根据我是使用基于异步/等待的代码还是基于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中完成。
发布于 2015-03-12 15:48:18
在async方法中,CallContext在写时被复制:
当异步方法启动时,它会通知其逻辑调用上下文,以激活复制到写入行为。这意味着当前的逻辑调用上下文实际上没有更改,但标记为如果代码确实调用了
CallContext.LogicalSetData,则逻辑调用上下文数据在更改之前被复制到一个新的当前逻辑调用上下文中。
来自隐式异步上下文("AsyncLocal")
这意味着在您的async版本中,延续是冗余的,即使没有它:
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 in Main不包含"hello"插槽:
{ Place = Task.Run,Id = 3,Msg = world } { Place = Main,Id = 1,Msg =}
在TPL等价物中,Task.Run以外的所有代码(应该是Task.Factory.StartNew,因为Task.Run是在.Net 4.5中添加的)运行在同一个线程上,与完全相同的如果您想清理它,您需要在这个上下文中这样做(而不是在继续中):
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;
}
}
}https://stackoverflow.com/questions/29001266
复制相似问题