System.Reflection.Emit是如何线程安全的?

本文关键字:线程 安全 Emit Reflection 何线程 System | 更新日期: 2023-09-27 18:12:24

我用动态代码生成和System.Reflection.Emit弄湿了我的脚。这一切似乎都很简单明了,但是有一个问题我在网上找不到答案。

当使用AssemblyBuilder构建动态程序集时,可以创建一个类型,然后立即开始使用它。如果以后您需要向程序集中添加另一种类型,您也可以这样做,并且一切都很好(据我所知)。

但是如果有两个线程试图为该程序集构建类型呢?或者如果一种现成的类型已经在使用,而另一种正在制作中呢?会有任何冲突或竞争条件吗?

添加:好的,我认为有必要多了解一些信息。

我用Reflection.Emit做的是在运行时动态实现接口。基本上是这样的:

class MagicClass
{
    public static T GetImplementation<T>();
}

其中T需要是一个接口(以及其他一些不相关的要求)。

每个接口将恰好有一个实现,该实现将恰好有一个实例。它们都是线程安全的单例。

因此,当一个新的,迄今为止未见过的接口的请求进来时,我实现它,创建一个实例,然后永久缓存它(这里定义为"直到我的程序停止")。

然而,这将在ASP中完成。NET web应用程序,所以我们总是有很多线程请求接口。性能很重要,我想弄清楚我能负担得起多少多线程。

很明显,单个接口只能由单个线程实现。但是我能同时在两个不同的线程中实现两个不同的接口吗?我想答案是——"最好不要"。

好的,所以我可以添加一个锁,只有一个线程在同一时间做一个实现。这样就能避免建造者之间的相互干扰。但是那些以前实现过的接口呢?当我们创建一个新线程时,其他线程正在同时使用它们。我想他们很好,除非他们试图在自己的组装上使用某种反射?

当然,我可以制定一个接口实现一个程序集的策略,但这将很快破坏"加载的模块"调试器窗口。此外,我不知道有100个加载的程序集会有什么性能影响(但我怀疑它们会很好)。

新增2:对,我的语言一定有问题,因为人们似乎不明白。让我用一个代码例子来试试:

var object1 = MagicClass.GetImplementation<I1>();
DoSomethingInAnotherThread(object1);
var object2 = MagicClass.GetImplementation<I2>();

DoSomethingInAnotherThread(object1)MagicClass.GetImplementation<I2>()之间是否存在线程相关错误?

我们可以假设:

  • DoSomethingInAnotherThread(object1)不调用MagicClass.GetImplementation<T>()
  • DoSomethingInAnotherThread(object1)没有在object1上使用反射,object1本身也没有。

基本上,问题是-可以一个无害的调用object1.SomeMethod()爆炸,因为程序集正在另一个线程上重组。

System.Reflection.Emit是如何线程安全的?

唯一声明的注释是"此类型的任何公共静态(在Visual Basic中共享)成员都是线程安全的。不能保证任何实例成员都是线程安全的,所以我们不能做太多的假设。

以下是通过检查IL的完整实现细节,不能用于除轶事以外的任何事情。

检查DefineDynamicModule,它使用lock, DefineType也是如此-事实上,它使用与父程序集构建器相同的SyncLock

TypeBuilder内部的一些东西也这样做(DefineEvent, DefineMethod, DefineField等);但是有些东西,比如DefineCustomAttribute最终变成了extern代码,所以我们无法分辨;然而,我在通过的路上看不到lock,并且它没有通过extern方法的锁。

总的来说,看起来似乎已经考虑了线程安全,但很可能他们不想正式保证它。看起来大多数常见的东西应该没问题,但是,特别是如果你从来没有多个线程在同一类型上工作。

强调:这些都是完全特定于实现的,我们没有一组"这个用例是保证工作的,但这还没有被考虑过"。

我个人倾向于保持简单;一个构建器(每个组件)通常很好。

未指定

当你使用没有显式文档的代码来保证线程安全时,你必须做出选择。权衡一下不加锁、锁错了以及代码以完全随机的方式崩溃的代价。相对于取锁的代价,确保永远不会出错,只是慢一点。

"有点慢"是你必须知道的东西,以做出正确的选择。在这种情况下,这是一个简单的方法,构建一个方法最多只需要几微秒。你放在它周围的锁是而不是将会被争夺。如果是这样,那么产生的延迟对任何人来说都是不可察觉的。线程上下文切换或页面错误,导致代码运行速度变慢的正常事情,会花费更多的时间。

把锁拿走。你永远不会后悔。

基本上问题是,对object1. someemethod()的无害调用是否会因为在另一个线程上重新组织程序集而失败。

从城堡的经验。核心包,它使用System.Reflection.Emit来生成类型,我很确定是的,它总是可以和有时会爆炸。我通常看到的症状是,当您试图使用生成的类型之一时,您得到TypeLoadExceptionBadImageFormageException