是否应该恢复Thread.CurrentContext?

与这个问题有关,

正在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
    }
}
  • async / await情况下,上下文不会被恢复,所以await之后的其余代码最终会在不同的上下文中执行。

  • .ContinueWith情况下,上下文不会被tpl恢复,而是上下文最终得到恢复,因为lambda最终被转入类成员方法。 如果lambda没有使用成员变量,那么在这种情况下,上下文也不会被恢复。

  • 看来由于这个原因,使用ContextBoundObjectasync / await或continuations会导致意外的行为。 例如,考虑我们是否在使用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发布给它的所有工作。


    显然, Thread.CurrentContext不会流动。 在.NET参考资源中,看到实际流入的是ExecutionContext一部分,这很有趣。 特别有趣的是,同步上下文如何通过ExecutionContext.Run显式流动,但不是隐式地通过Task.Run

    我不确定自定义的同步上下文(例如AspNetSynchronizationContext ),默认情况下,它可以AspNetSynchronizationContextExecutionContext更多的线程属性。

    这是一个很棒的阅读,相关:“ExecutionContext vs SynchronizationContext”。

    更新后 ,即使您想手动执行(使用Stephen Toub的WithCurrentCulture ),也不会出现Thread.CurrentContext可以完全流动。 检查System.Runtime.Remoting.Contexts.Context的实现,显然它不是被设计为被复制到另一个线程(与SynchronizationContextExecutionContext不同)。

    我不是.NET Remoting的专家,但我认为ContextBoundObject派生的对象需要线程亲和力。 也就是说,他们会在同一个线程中创建,访问和销毁它们的一生。 我相信这是ContextBoundObject设计要求的一部分。

    根据@ MattSmith的更新更新

    Matt,你的绝对正确,当从另一个域调用ContextBoundObject对象时,没有线程关联。 如果在类上指定了[Synchronization]则跨不同线程或上下文访问整个对象将被序列化。

    就我所知,线程和上下文之间也没有逻辑关系。 上下文是与对象关联的东西。 可以在同一个线程上运行多个上下文(与COM公寓不同),以及共享相同上下文的多个线程(类似于COM公寓)。

    使用Context.DoCallback ,确实有可能在await之后继续使用相同的上下文,或者使用自定义的awaiter(如下面的代码所示),或者使用自定义同步上下文,就像您在问题中提到的一样。

    我玩的代码是:

    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);
            }
        }
    }
    
    链接地址: http://www.djcxy.com/p/78275.html

    上一篇: Is await supposed to restore Thread.CurrentContext?

    下一篇: Lambda capture parameter provoking ambiguity