【问题标题】:Threading/Ambient Context in CRM 2011 pluginsCRM 2011 插件中的线程/环境上下文
【发布时间】:2012-12-02 21:18:15
【问题描述】:

我们最近遇到过几次这样的问题:在 Dynamics CRM 2011 中,是否保证一个插件执行(即 Execute() 方法的传递)保持在同一个线程上。

我想使用环境上下文模式来实现跟踪,以避免将跟踪服务传递给任何可能想要跟踪的类。问题是我们知道插件每个注册步骤只实例化一次,然后从同一个实例提供所有后续操作;这意味着我不能只拥有一些像Tracing.Current 这样的静态属性,我将当前的ITracingService 实例分配给它,我很高兴。如果我这样做了,最后开始的操作将覆盖所有其他可能仍在运行的操作的实例(这种并发并不少见)。

现在,如果我可以确定 Execute() 方法下的所有内容都保留在同一个线程中,我仍然可以使用将 [ThreadStatic] 属性用于静态字段的环境上下文:

public static class Tracing
{
    [ThreadStatic]
    private static ITracingService _current;

    public static ITracingService Current
    {
        get
        {
            if (null == _current)
            {
                _current = new NullTracingService();
            }

            return _current;
        }

        set { _current = value; }
    }
}

我会在输入Execute() 方法时设置它,并在最后清除它,以便删除对跟踪服务实例的引用。

我唯一能有点了解 MSCRM 插件上下文中的线程是,显然各个线程来自 ThreadPool - 可能对我的问题产生的任何后果。

有没有人更深入地了解如何使用 MSCRM 插件处理线程 - 或者关于如何在这种特殊情况下使用 SOLID 代码优雅地处理跟踪的横切关注点的任何其他想法(AOP/动态拦截不是选项在这里)?

感谢您的帮助和指点。

【问题讨论】:

  • 我理解您对以线程安全方式运行的担忧,但我不确定我是否理解您为什么担心在同一个线程上运行。你能再解释一下吗?
  • 好吧,我需要避免插件执行被移动到另一个线程,而没有再次“附加”它自己的 ITracingService 实例。我还没有弄清楚在这种情况下如何处理 ThreadStatic。

标签: c# multithreading dynamics-crm-2011 threadpool cross-cutting-concerns


【解决方案1】:

简单而聪明的答案:如果这样做时会感到痛苦,那就不要这样做。 :)

您对使用环境上下文模式的自我要求与 CRM 的设计模式相冲突。想一想 CRM 的工作原理 - 它会向您传递一个 IServiceProvider,其中包含您需要的一切,包括跟踪服务。它为您处理所有复杂的多线程和优化,并且只要求您不要试图用花哨的模式或静态变量或线程技巧来超越它。

我的建议是使用相同的模式 - 将 IServiceProvider 传递给任何需要它的类或方法。简单得多 - 再加上以后遇到奇怪的错误时,您不会质疑您是否成功地超越了 Microsoft 的工程师。 :)

【讨论】:

  • 我同意,人们应该始终警惕将事情做得太过分或以非设计用于使用的方式使用系统。不过,我看不出跟踪服务会涉及到任何“复杂的多线程”。它要么存在,要么不存在——我正在寻找一种确保它存在的方法。 ;-) 顺便说一句,自我强加的要求实际上是好的、可维护的软件设计。环境上下文只是达到目的的一种手段,如果可能的话,我很乐意不使用它。
  • 是否只是为了编写 Tracing.Current 而不是每次都编写代码从 IServiceProvider 中提取 Tracing 服务?如果是这样,也许可以考虑从 ServiceProvider 对象中编写一个 C# 扩展方法,以便轻松获取对跟踪服务的引用。或者使用代码 sn-p 为您粘贴该样板代码 - 它不太可能改变,如果改变,它是一个简单的查找/替换。最重要的是,不要再花超过 10 分钟的时间,开始解决真正的业务问题,伙计!我很高兴我没有付钱让你担心这个! :)
【解决方案2】:

CRM 创建单个插件对象,然后根据需要使用线程来处理请求。因此,您唯一可以确定的是,您将同时为单个插件对象运行多个线程。

线程是通过 IIS 管理的,如果可能的话会被重用。因此,如果您想确保每次调用 Execute 时,它都有一个新的 ITracingService 您必须设置它。如果您只想确保每次调用 Execute 时都有一个,您只需执行 if 语句来检查它。

由于您的后备变量是ThreadStatic,您无需担心线程问题,但由于 IIS 尝试重用线程,因此每次调用 Execute 时它都不会为空。

【讨论】:

    【解决方案3】:

    恐怕我只能在这里猜测整个插件/线程/静态问题,但是您提出的建议似乎有点复杂。那么作为替代方案,您是否考虑过使用跟踪侦听器?

    如果您在整个应用程序中使用Trace.Writeline,则单个跟踪侦听器将捕获所有这些消息。这样您就不必传递跟踪对象。

    例如:

    Execute(...)
    {
        if(System.Diagnostics.Trace.Listeners
            .Count(l => typeof(l) == MyCustomTraceListener) == 0)
        {
            System.Diagnostics.Trace.Listeners.Add(new MyCustomTraceListener());
        }
    
        DoWork();
    }
    
    DoWork()
    {   
        System.Diagnostics.Trace.WriteLine("I'm doing work!");
    }
    

    相关链接:

    Trace ListenersWalkthrough: Creating a Custom Trace Listener

    【讨论】:

    • 我已经阅读过它们,但完全忽略了它们。我认为那些是为整个进程/AppDomain 注册的,对吧?在同一个插件实例上对Execute() 的每次调用都会带来它自己的 ITracingService 实例,并且必须保证只使用那个实例。我正在寻找一种方法来确保不会污染我的大多数构造函数,而该参数实际上并不是各个类执行其工作所必需的。
    • 我完全不知道,我将不得不在 MSDN 周围进行挖掘。我了解您的情况,我自己一直在那里传递记录对象的所有函数和类。我会考虑一下进程/AppDomain 位。
    猜你喜欢
    • 2014-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多