根据我使用的是基于异步/等待的代码还是基于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/