如何使用 AppDomain 限制静态类的范围以进行线程安全使用?

Posted

技术标签:

【中文标题】如何使用 AppDomain 限制静态类的范围以进行线程安全使用?【英文标题】:How to use an AppDomain to limit a static class' scope for thread-safe use? 【发布时间】:2010-09-23 08:46:37 【问题描述】:

我被架构不佳的解决方案所困扰。它不是线程安全的!

我在解决方案中有几个共享的类和成员,在开发过程中一切都很酷... BizTalk 击沉了我的战舰。

我们正在使用自定义 BizTalk 适配器来调用我的程序集。适配器正在调用我的代码并并行运行,所以我假设它在同一个 AppDomain 下使用多个线程。

我想做的是让我的代码在它自己的 AppDomain 下运行,这样我遇到的共享问题就不会相互混淆。

我有一个非常简单的类,BizTalk 适配器正在实例化然后运行 ​​Process() 方法。

我想在我的 Process() 方法中创建一个新的 AppDomain,因此每次 BizTalk 旋转另一个线程时,它都会有自己的静态类和方法版本。

BizTalkAdapter 代码:

  // this is inside the BizTalkAdapter and it is calling the Loader class //
  private void SendMessage(IBaseMessage message, TransactionalTransmitProperties properties)
    

        Stream strm = message.BodyPart.GetOriginalDataStream();
        string connectionString = properties.ConnectionString;
        string msgFileName = message.Context.Read("ReceivedFileName", "http://schemas.microsoft.com/BizTalk/2003/file-properties") as string;


        Loader loader = new Loader(strm, msgFileName, connectionString);
        loader.Process();

        EventLog.WriteEntry("Loader", "Successfully processed: " + msgFileName);

    

这是类 BizTalk 调用:

public class Loader


    private string connectionString;
    private string fileName;
    private Stream stream;
    private DataFile dataFile;

    public Loader(Stream stream, string fileName, string connectionString)
    
        this.connectionString = connectionString;
        this.fileName = fileName;
        this.stream = stream;
      

    public void Process()
    

        //*****  Create AppDomain HERE *****
        // run following code entirely under that domain
        dataFile = new DataFile(aredStream, fileName, connectionString);
        dataFile.ParseFile();
        dataFile.Save();
        // get rid of the AppDomain here...

    


仅供参考:Loader 类位于与 dataFile 类不同的 DLL 中。

任何帮助将不胜感激。我将继续致力于使代码线程安全,但我觉得这可能是“简单”的答案。

如果有人有其他想法,请提出来。

谢谢你, 基思

只是为了完整性。

我确实发现,如果我在 “传输高级选项”对话框我能够避免 我遇到的多线程问题。

我认为这是我的问题的另一个可能答案,但不是 一定要问这个问题。

【问题讨论】:

所以你说的不是静态类和对象,你说的是单个实例,对吧? 【参考方案1】:

为每个调用创建和拆除一个应用域 - 我认为您不担心这个调用的性能吗?

理想情况下,您应该将调用的代码更改为线程安全的。

【讨论】:

【参考方案2】:

只是为了完整性。

我确实发现,如果我在“传输高级选项”对话框中将发送适配器标记为“有序交付”,我能够避免我遇到的多线程问题。

我认为这是我的问题的另一个可能答案,但不一定是问题的答案。

【讨论】:

【参考方案3】:

使用应用程序域,您可以执行以下操作:

public class Loader


    private string connectionString;
    private string fileName;
    private Stream stream;
    private DataFile dataFile;

    public Loader(Stream stream, string fileName, string connectionString)
    
        this.connectionString = connectionString;
        this.fileName = fileName;
        this.stream = stream;
      

    public void Process()
    
        //*****  Create AppDomain HERE *****
        string threadID = Thread.CurrentThread.ManagedThreadId.ToString();
        AppDomain appDomain = AppDomain.CreateDomain(threadID);

        DataFile dataFile = 
            (DataFile) appDomain.CreateInstanceAndUnwrap(
                        "<DataFile AssemblyName>", 
                        "DataFile", 
                        true, 
                        BindingFlags.Default,
                        null,
                        new object[] 
                         
                            aredstream, 
                            filename, 
                            connectionString 
                        ,
                        null,
                        null,
                        null);
        dataFile.ParseFile();
        dataFile.Save();

        appDomain.Unload(threadID);       
    

【讨论】:

Kev,如果您使用 threadID 作为域名,同一线程第二次使用此类是否会创建相同的域名?如果是这样,这两个 AppDomain 是否会因名称相同而彼此不同?基思 我想既然您正在卸载域,那就没问题了。我担心的静态会在那时被 GC 处理。对吗? 字符串 threadID = Thread.CurrentThread.ManagedThreadId.ToString(); 如果同一个线程第二次使用 Loader 类和 Process 方法,访问将被序列化,因为线程不能同时在两个地方左右。也感谢您的更正。 DataFile 类使用的所有静态变量都是应用域本地的,并且在应用域被拆除时也会被清理。【参考方案4】:

如果您有相互冲突的共享静态,那么您可能想尝试向它们添加 [ThreadStatic] 属性。这将使它们成为每个线程的本地。这可能会在短期内解决您的问题。一个正确的解决方案是简单地将你的东西重新架构为线程安全的。

【讨论】:

【参考方案5】:

在线程安全方面,究竟哪一点是痛苦的?我看不到任何静态或单例 - 似乎有合适的“新”对象......我是瞎了吗?

那么你看到的症状是什么...

AppDomain 的回答会(相对)慢。作为中间件支持的系统的一部分,这可能没问题(即“相对”在同一个球场)。

如果您确实在某处有一些静态状态,则另一个有时有效的选项是 [ThreadStatic] - 运行时将其解释为“此静态字段对于每个线程都是唯一的”。不过,您需要小心初始化 - 线程 A 上的静态构造函数可能会分配一个字段,但随后线程 B 会看到 null/0/等。

【讨论】:

我在dataFile下面有一些静态类,在解析逻辑和保存逻辑里面。我会看看这个 ThreadStatic 看看它是否有帮助。谢谢你。基思【参考方案6】:

为什么不对要按顺序执行的代码加锁呢? 这将是一个瓶颈,但它应该在多线程环境中工作。

public class Loader

    private static object SyncRoot = new object();
    private string connectionString;
    private string fileName;
    private Stream stream;
    private DataFile dataFile;

    public Loader(Stream stream, string fileName, string connectionString)
    
        this.connectionString = connectionString;
        this.fileName = fileName;
        this.stream = stream;
      

    public void Process()
    

        lock(SyncRoot) 
            dataFile = new DataFile(aredStream, fileName, connectionString);
            dataFile.ParseFile();
           dataFile.Save();
        

    


【讨论】:

我想到了那个。。锁会导致其他线程等待,但是共享类没有清理。他们呆在周围会引起头痛。

以上是关于如何使用 AppDomain 限制静态类的范围以进行线程安全使用?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 AppDomain 内的程序集中调用静态方法 [重复]

如何从另一个 appDomain 调用类的方法

如何在 AppDomain 下创建类的新实例

卸载 appdomain 不会清除 C++ COM 对象静态成员

静态类的范围是啥?

在 .NET 中,创建新的 AppDomain 时是不是调用静态构造函数?