gpt4 book ai didi

c# - 等待应该恢复 Thread.CurrentContext 吗?

转载 作者:太空狗 更新时间:2023-10-29 20:30:44 27 4
gpt4 key购买 nike

this question相关,

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 不使用成员变量,在这种情况下也不会恢复上下文。

似乎正因为如此,使用async/awaitContextBoundObject 的延续s 会导致意想不到的行为。例如,考虑一下我们是否使用了 [Synchronization]使用 async 的类的属性 ( MSDN doc)/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) 来执行 await 之后的部分。 .

DoCallback做恢复上下文的工作。具体恢复上下文的工作是什么取决于具体的上下文。

基于此,似乎可能可以通过创建自定义 SynchronizationContext 来实现此行为。它包装了在调用 DoCallback 时发布给它的任何工作.

最佳答案

显然,Thread.CurrentContext 没有流动。有趣的是,作为 ExecutionContext 的一部分,实际得到了什么流,here in .NET reference sources .特别有趣的是,同步上下文如何通过 ExecutionContext.Run 显式流动,而不是通过 Task.Run 隐式流动。

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

这是一篇很棒的相关读物:"ExecutionContext vs SynchronizationContext" .

已更新Thread.CurrentContext 似乎根本无法流动,即使 如果您想这样做手动(使用 Stephen Toub 的 WithCurrentCulture 之类的东西)。检查 System.Runtime.Remoting.Contexts.Context 的执行情况,显然它不是为复制到另一个线程而设计的(与 SynchronizationContextExecutionContext 不同)。

我不是 .NET 远程处理方面的专家,但我认为 ContextBoundObject 派生的对象需要线程关联。即,它们在其生命周期内在同一个线程上创建、访问和销毁。我相信这是 ContextBoundObject 设计要求的一部分。

更新,基于@MattSmith 的更新

Matt,您说得对,当从不同的域调用时,基于 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);
}
}
}

关于c# - 等待应该恢复 Thread.CurrentContext 吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22416182/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com