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
.
In an async
method the CallContext
is copied on write:
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.
From Implicit Async Context ("AsyncLocal")
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")
}));
}
The CallContext
in Main
wouldn't contain the "hello"
slot:
{ Place = Task.Run, Id = 3, Msg = world }
{ Place = Main, Id = 1, Msg = }
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")
}));
}
}
Using:
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;
}
}
}