与此问题相关,
是await应该恢复的背景下(特别是通过上下文所代表Thread.CurrentContext的)ContextBoundObject?考虑以下内容:
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
}
}
Run Code Online (Sandbox Code Playgroud)
在async/ awaitcase中,不恢复上下文,因此await之后的剩余代码最终在不同的上下文中执行.
在这种.ContinueWith情况下,tpl不会恢复上下文,而是由于lambda最终被转入类成员方法,因此上下文最终得到恢复.如果lambda没有使用成员变量,那么在这种情况下也不会恢复上下文.
似乎由于这个原因,使用async/ await或继续使用ContextBoundObjects将导致意外的行为.例如,考虑我们是否在使用/ 的类上使用了[Synchronization]属性(MSDN doc).同步保证不会在第一个之后应用于代码.asyncawaitawait
回应@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);
}
Run Code Online (Sandbox Code Playgroud)
如果await要恢复上下文,我的期望不是将Context"复制"到新线程,而是它将类似于SynchronizationContext恢复的方式.基本上,您希望在当前捕获当前的Context await,然后您希望通过调用执行await之后的部分capturedContext.DoCallback(afterAwaitWork).
这DoCallback是恢复上下文的工作.确切地说,恢复上下文的工作取决于具体的上下文.
在此基础上,它似乎可能可以通过创建一个自定义实现这个行为SynchronizationContext,它封装发布到它的调用任何工作DoCallback.
显然,Thread.CurrentContext不会流动。有趣的是ExecutionContext,在 .NET 参考源中看到实际作为 的一部分流动的内容。特别有趣的是同步上下文如何通过 显式流动ExecutionContext.Run,而不是隐式使用Task.Run。
我不确定自定义的同步上下文(例如AspNetSynchronizationContext),它可能比ExecutionContext默认情况下流动更多的线程属性。
这是一个很棒的阅读,相关:“ExecutionContext vs SynchronizationContext”。
已更新,即使您想手动进行(使用 Stephen Toub 之类的东西),它似乎也Thread.CurrentContext无法流动。检查 的实现,显然它不是设计为复制到另一个线程(与或不同)。WithCurrentCultureSystem.Runtime.Remoting.Contexts.ContextSynchronizationContextExecutionContext
我不是 .NET 远程处理方面的专家,但我认为ContextBoundObject派生对象需要线程关联。即,在它们的生命周期中,它们在同一个线程上被创建、访问和销毁。我相信这是ContextBoundObject设计要求的一部分。
更新,基于@MattSmith 的更新。
马特,您说得对,ContextBoundObject当从不同的域调用基于对象的对象时,它没有线程关联。如果[Synchronization]在类上指定,则跨不同线程或上下文对整个对象的访问将被序列化。
据我所知,线程和上下文之间也没有逻辑联系。上下文是与对象相关联的东西。可以在同一线程上运行多个上下文(与 COM 公寓不同),并且多个线程共享相同的上下文(类似于 COM 公寓)。
使用Context.DoCallback,确实可以在 之后继续使用相同的上下文await,可以使用自定义等待程序(如下面的代码中所述),也可以使用自定义同步上下文,如您在问题中提到的。
我玩的代码:
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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1656 次 |
| 最近记录: |