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/awaitcase, the context isn't restored, so the remaining code after the await ends up executing in a different context.In the
.ContinueWithcase, 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 ContextBoundObjects 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.