与这个问题相关,
await
是否应该恢复ContextBoundObject
表示的上下文(特别是由Thread.CurrentContext
表示的上下文)?请考虑以下内容:
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
}
}
在
async
/await
情况下,上下文不会被恢复,因此await后的剩余代码将在不同的上下文中执行。在
.ContinueWith
情况下,上下文不会被tpl恢复,而是由于lambda最终被转换为类成员方法而导致上下文得到恢复。如果lambda没有使用成员变量,则在那种情况下上下文也不会得到恢复。
由于此原因,使用async
/await
或具有ContextBoundObject
的连续处理将导致意外结果。例如,考虑如果我们在使用async
/await
的类上使用了[Synchronization]
属性 (MSDN doc)。同步保证将不适用于第一个await
后的代码。
回应 @Noseratio
ContextBoundObjects
不一定(默认情况下)需要线程亲和性。在我给出上下文相同的示例中,您不会处于相同的线程上(除非您很幸运)。您可以使用Context.DoCallBack(...)
在上下文中进行工作。这不会将您放回原始线程(除非Context
为您执行此操作)。这里是一个修改了的Class1
示例:
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);
}
如果
await
恢复上下文,则我的期望不是将上下文“复制”到新线程,而是类似于如何恢复SynchronizationContext
。基本上,您需要在await
处捕获当前上下文,然后通过调用capturedContext.DoCallback(afterAwaitWork)
来执行await
后面的部分。
DoCallback
完成了恢复上下文的工作。恢复上下文的确切工作取决于特定上下文。基于这一点,似乎可以通过创建自定义
SynchronizationContext
来获取此行为,该SynchronizationContext在发布任何工作时都会包装在对DoCallback
的调用中。
Context
并不(必须)意味着特定线程(而且ContextBoundObject
不需要线程亲和性)。虽然可以设置需要线程亲和性的Context
。请参见我上面的编辑。 - Matt SmithContextAwaiter
。我希望在某个时候发布一个更新,尝试使用ContextSynchronizationContext
方法。如果我这样做了,我会联系你并请你查看。再次感谢。 - Matt Smith