正在伺机应该恢复Thread.CurrentContext? [英] Is await supposed to restore Thread.CurrentContext?
问题描述
相关这个问题,
是等待
应该恢复环境(特别是再由 Thread.CurrentContext
psented $ P $的上下文中)为 ContextBoundObject
?考虑下面的:
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
}
}
-
在
异步
/等待
情况下,上下文是不可恢复的,因此剩下的$ C $ç后的await结束了在不同的上下文中执行。In the
async
/await
case, the context isn't restored, so the remaining code after the await ends up executing in a different context.在
.ContinueWith
的情况下,上下文不被TPL恢复,而是上下文最终得到恢复,由于这样的事实:在lambda最终被翻在地类的成员方法。有拉姆达没有使用一个成员变量,上下文不会在这种情况下,无论是恢复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.看来,缘于此,使用
异步
/等待
或ContextBoundObject延续
旨意导致意外beahvior。例如,考虑一下,如果我们使用了[同步]
属性(<一个href=\"http://msdn.microsoft.com/en-us/library/system.runtime.remoting.contexts.synchronizationattribute%28v=vs.110%29.aspx\"相对=关于使用异步
/nofollow的> MSDN文档)等待
。后同步担保并不适用于code中的第一个等待
。It seems that due to this, using
async
/await
or continuations withContextBoundObject
s will result in unexpected beahvior. For example, consider if we had used the[Synchronization]
attribute (MSDN doc) on a class that usesasync
/await
. The Synchronization guarantees would not apply to code after the firstawait
.在应对@Noseratio
ContextBoundObjects
不(一定或默认)要求线程关联。在这个例子中,我给那里的情况下最终被同样的,你最终不会是相同的线程上(除非你是幸运的)。您可以使用Context.DoCallBack(...)
来在上下文中找到工作。这会不会让你到原来的线程(除非上下文
会替你)。下面是修改1级
这表明: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 useContext.DoCallBack(...)
to get work within a context. This won't get you on to the original thread (unless theContext
does that for you). Here's a modification ofClass1
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); }
如果
等待
被恢复的背景下,我的期望不会是上下文会被复制到新的线程,而是将类似于如何在的SynchronizationContext
恢复。基本上,你会希望将当前上下文的等待
,然后你想要的部分后,通过调用执行的awaitcapturedContext被捕获.DoCallback(afterAwaitWork)
。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 theSynchronizationContext
is restored. Basically, you would want the current Context to be captured at theawait
, and then you would want the part after the await to be executed by callingcapturedContext.DoCallback(afterAwaitWork)
.的
DoCallback
确实恢复环境的工作。正是恢复上下文的工作是依赖于特定上下文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.,似乎它的可能的可以通过创建一个自定义来获得这一行为
的SynchronizationContext
它包装张贴到任何工作在调用DoCallback
。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 toDoCallback
.推荐答案
显然,
Thread.CurrentContext
没有得到流动。这是有趣的,看看什么是真正得到流动为的ExecutionContext
的一部分,<一个href=\"http://referencesource.microsoft.com/#mscorlib/system/threading/executioncontext.cs#3a777fa35a9f1ffe\"相对=在.NET参考来源nofollow的>在这里。特别有趣的是如何同步的情况下被通过ExecutionContext.Run
明确流向,而不是隐式Task.Run
。Apparently,
Thread.CurrentContext
doesn't get flowed. It's interesting to see what actually gets flowed as part ofExecutionContext
, here in .NET reference sources. Especially interesting how synchronization context gets flowed explicitly viaExecutionContext.Run
, but not implicitly withTask.Run
.我不知道定制同步上下文(如
AspNetSynchronizationContext
),这可能比的ExecutionContext
(默认设置)。I'm not sure about customized synchronization contexts (e.g.
AspNetSynchronizationContext
), which may flow more thread's properties thanExecutionContext
does by default.下面是一个伟大的阅读,相关的:<一href=\"http://blogs.msdn.com/b/pfxteam/archive/2012/06/15/executioncontext-vs-synchronizationcontext.aspx\"相对=nofollow>的ExecutionContext VS的SynchronizationContext。
Here is a great read, related: "ExecutionContext vs SynchronizationContext".
更新后,它不会出现Thread.CurrentContext
可在所有流过,的甚至如果你想手工做(的东西,如斯蒂芬Toub的的WithCurrentCulture
)。检查实施<一个href=\"http://referencesource.microsoft.com/#mscorlib/system/runtime/remoting/context.cs#28f5043928414712\"相对=nofollow>System.Runtime.Remoting.Contexts.Context
,显然它不是设计被复制到另一个线程(不像的SynchronizationContext
或的ExecutionContext
)。Updated, it doesn't appear thatThread.CurrentContext
could be flowed at all, even if you wanted to do it manually (with something like Stephen Toub'sWithCurrentCulture
). Check the implementation ofSystem.Runtime.Remoting.Contexts.Context
, apparently it is not designed to be copied to another thread (unlikeSynchronizationContext
orExecutionContext
).我不是.NET远程的专家,但我觉得
ContextBoundObject
派生的对象都需要线程关联。即,他们得到创建,访问并摧毁了同一个线程,对他们的一生。我相信这是ContextBoundObject
设计要求的一部分。I'm not an expert in .NET remoting, but I think
ContextBoundObject
-derived objects require thread affinity. I.e., they get created, accessed and destroyed on the same thread, for their lifetime. I believe this is a part ofContextBoundObject
design requirements.更新的基础上,@马特·史密斯的更新
马特,your're绝对正确的,有一个
ContextBoundObject
没有线程亲和力为基础的对象时,它是从不同的域调用。在不同的线程或上下文整个对象的访问被序列化,如果[同步]
的类指定的。Matt, your're absolutely right, there is no thread affinity for
ContextBoundObject
-based objects when it's called from a different domain. The access to the whole object across different threads or contexts gets serialized if[Synchronization]
is specified on the class.还有线程和上下文之间没有逻辑联系,据我可以告诉。上下文是与对象关联的东西。可以有相同的线程上运行多个上下文(不像COM公寓)和多线程共享相同的上下文(类似于COM公寓)。
There is also no logical connection between threads and contexts, as far as I can tell. A context is something associated with an object. There can be multiple contexts running on the same thread (unlike with COM apartments), and multiple threads sharing the same context (similar to COM apartments).
使用
Context.DoCallback
,确实有可能继续在同一环境后等待
,无论是与一个定制awaiter(如下code完成),或用自定义同步情况下,你在你的问题中提到。Using
Context.DoCallback
, it is indeed possible to continue on the same context afterawait
, either with a custom awaiter (as done in the code below), or with a custom synchronization context, as you mentioned in your question.在code我打了:
using System; using System.Runtime.Remoting.Contexts; using System.Threading; using System.Threading.Tasks; namespace ConsoleApplication { public class Program { [Synchronization] public class MyController: ContextBoundObject { /// All access to objects of this type will be intercepted /// and a check will be performed that no other threads /// are currently in this object's synchronization domain. int i = 0; public void Test() { Console.WriteLine(String.Format("\nenter Test, i: {0}, context: {1}, thread: {2}, domain: {3}", this.i, Thread.CurrentContext.ContextID, Thread.CurrentThread.ManagedThreadId, System.AppDomain.CurrentDomain.FriendlyName)); Console.WriteLine("Testing context..."); Program.TestContext(); Thread.Sleep(1000); Console.WriteLine("exit Test"); this.i++; } public async Task TaskAsync() { var context = Thread.CurrentContext; var contextAwaiter = new ContextAwaiter(); Console.WriteLine(String.Format("TaskAsync, context: {0}, same context: {1}, thread: {2}", Thread.CurrentContext.ContextID, Thread.CurrentContext == context, Thread.CurrentThread.ManagedThreadId)); await Task.Delay(1000); Console.WriteLine(String.Format("after Task.Delay, context: {0}, same context: {1}, thread: {2}", Thread.CurrentContext.ContextID, Thread.CurrentContext == context, Thread.CurrentThread.ManagedThreadId)); await contextAwaiter; Console.WriteLine(String.Format("after await contextAwaiter, context: {0}, same context: {1}, thread: {2}", Thread.CurrentContext.ContextID, Thread.CurrentContext == context, Thread.CurrentThread.ManagedThreadId)); } } // ContextAwaiter public class ContextAwaiter : System.Runtime.CompilerServices.INotifyCompletion { Context _context; public ContextAwaiter() { _context = Thread.CurrentContext; } public ContextAwaiter GetAwaiter() { return this; } public bool IsCompleted { get { return false; } } public void GetResult() { } // INotifyCompletion public void OnCompleted(Action continuation) { _context.DoCallBack(() => continuation()); } } // Main public static void Main(string[] args) { var ob = new MyController(); Action<string> newDomainAction = (name) => { System.AppDomain domain = System.AppDomain.CreateDomain(name); domain.SetData("ob", ob); domain.DoCallBack(DomainCallback); }; Console.WriteLine("\nPress Enter to test domains..."); Console.ReadLine(); var task1 = Task.Run(() => newDomainAction("domain1")); var task2 = Task.Run(() => newDomainAction("domain2")); Task.WaitAll(task1, task2); Console.WriteLine("\nPress Enter to test ob.Test..."); Console.ReadLine(); ob.Test(); Console.WriteLine("\nPress Enter to test ob2.TestAsync..."); Console.ReadLine(); var ob2 = new MyController(); ob2.TaskAsync().Wait(); Console.WriteLine("\nPress Enter to test TestContext..."); Console.ReadLine(); TestContext(); Console.WriteLine("\nPress Enter to exit..."); Console.ReadLine(); } static void DomainCallback() { Console.WriteLine(String.Format("\nDomainCallback, context: {0}, thread: {1}, domain: {2}", Thread.CurrentContext.ContextID, Thread.CurrentThread.ManagedThreadId, System.AppDomain.CurrentDomain.FriendlyName)); var ob = (MyController)System.AppDomain.CurrentDomain.GetData("ob"); ob.Test(); Thread.Sleep(1000); } public static void TestContext() { var context = Thread.CurrentContext; ThreadPool.QueueUserWorkItem(_ => { Console.WriteLine(String.Format("QueueUserWorkItem, context: {0}, same context: {1}, thread: {2}", Thread.CurrentContext.ContextID, Thread.CurrentContext == context, Thread.CurrentThread.ManagedThreadId)); }, null); ThreadPool.UnsafeQueueUserWorkItem(_ => { Console.WriteLine(String.Format("UnsafeQueueUserWorkItem, context: {0}, same context: {1}, thread: {2}", Thread.CurrentContext.ContextID, Thread.CurrentContext == context, Thread.CurrentThread.ManagedThreadId)); }, null); } } }
这篇关于正在伺机应该恢复Thread.CurrentContext?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!