Ожидается ли ожидание восстановления 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 В этом случае контекст не восстанавливается, поэтому оставшийся код после ожидания заканчивается выполнением в другом контексте.

  • в .ContinueWith В этом случае контекст не восстанавливается с помощью tpl, но вместо этого контекст заканчивается восстановлением из-за того, что лямбда в конечном итоге превращается в метод члена класса. Если бы лямбда не использовала переменную-член, контекст в этом случае также не был бы восстановлен.

Кажется, что из-за этого, используя async / await или продолжения с ContextBoundObject s приведет к неожиданному поведению. Например, рассмотрим, использовали ли мы [Synchronization] атрибут ( документ MSDN) в классе, который использует async / await, Гарантии синхронизации не будут применяться к коду после первого 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),

DoCallback выполняет работу по восстановлению контекста. В чем именно заключается работа по восстановлению контекста, зависит от конкретного контекста.

Исходя из этого, кажется, что возможно получить такое поведение, создав SynchronizationContext который оборачивает любую работу, опубликованную на нем в вызове DoCallback,

1 ответ

Решение

По-видимому, Thread.CurrentContext не течет. Интересно посмотреть, что на самом деле происходит как часть ExecutionContext, вот в.NET справочные источники. Особенно интересно, как контекст синхронизации передается явно через ExecutionContext.Run, но не безоговорочно с Task.Run,

Я не уверен насчет пользовательских контекстов синхронизации (например, AspNetSynchronizationContext), который может передавать больше свойств потока, чем ExecutionContext делает по умолчанию.

Вот отличная статья: "ExecutionContext vs SynchronizationContext".

Обновлено, похоже, что Thread.CurrentContext может течь вообще, даже если вы хотите сделать это вручную (с чем-то вроде Стивена Туба WithCurrentCulture ). Проверьте выполнение System.Runtime.Remoting.Contexts.Context по-видимому, он не предназначен для копирования в другой поток (в отличие от SynchronizationContext или же ExecutionContext).

Я не эксперт в удаленном взаимодействии.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);
        }
    }
}
Другие вопросы по тегам