Related to this question,
Is await
supposed to restore the context (specifically the context represented by Thread.CurrentContext
) for a ContextBoundObject
? Consider the below:
class Program
{
static void Main(string[] args)
{
var c1 = new Class1();
Console.WriteLine("Method1");
var t = c1.Method1();
t.Wait();
Console.WriteLine("Method2");
var t2 = c1.Method2();
t2.Wait();
Console.ReadKey();
}
}
public class MyAttribute : ContextAttribute
{
public MyAttribute() : base("My") { }
}
[My]
public class Class1 : ContextBoundObject
{
private string s { get { return "Context: {0}"; } } // using a property here, since using a field causes things to blow-up.
public async Task Method1()
{
Console.WriteLine(s, Thread.CurrentContext.ContextID); // Context1
await Task.Delay(50);
Console.WriteLine(s, Thread.CurrentContext.ContextID); // Context0
}
public Task Method2()
{
Console.WriteLine(s, Thread.CurrentContext.ContextID); // Context1
return Task.Delay(50).ContinueWith(t => Console.WriteLine(s, Thread.CurrentContext.ContextID)); // Context1
}
}
In the
async
/await
case, the context isn't restored, so the remaining code after the await ends up executing in a different context.In the
.ContinueWith
case, the context isn't restored by the tpl, but instead the context ends up getting restored due to the fact that the lambda ends up being turned in to class member method. Had the lambda not used a member variable, the context wouldn't be restored in that case either.
It seems that due to this, using async
/await
or continuations with ContextBoundObject
s will result in unexpected beahvior. For example, consider if we had used the [Synchronization]
attribute (MSDN doc) on a class that uses async
/await
. The Synchronization guarantees would not apply to code after the first await
.
In Response to @Noseratio
ContextBoundObjects
don't (necessarily or by default) require thread affinity. In the example, I gave where the context ends up being the same, you don't end up being on the same thread (unless you are lucky). You can use Context.DoCallBack(...)
to get work within a context. This won't get you on to the original thread (unless the Context
does that for you). Here's a modification of Class1
demonstrating that:
public async Task Method1()
{
var currCtx = Thread.CurrentContext;
Console.WriteLine(s, currCtx.ContextID); // Context1
Console.WriteLine("Thread Id: {0}", Thread.CurrentThread.ManagedThreadId);
await Task.Delay(50);
currCtx.DoCallBack(Callback);
}
static void Callback()
{
Console.WriteLine("Context: {0}", Thread.CurrentContext.ContextID); // Context1
Console.WriteLine("Thread Id: {0}", Thread.CurrentThread.ManagedThreadId);
}
If await
were to restore the Context, my expectation would not be that the Context would be "copied" to the new thread, but rather it would be similar to how the SynchronizationContext
is restored. Basically, you would want the current Context to be captured at the await
, and then you would want the part after the await to be executed by calling capturedContext.DoCallback(afterAwaitWork)
.
The DoCallback
does the work of restoring the context. Exactly what the work of the restoring the context is is dependent on the specific context.
Based on that, it seems that it might be possible to get this behavior by creating a custom SynchronizationContext
which wraps any work posted to it in a call to DoCallback
.