Threading/Ambient Context in CRM 2011 plugins
Asked Answered
E

3

8

We have recently had a few occasions where the question came up whether in Dynamics CRM 2011, one plugin execution (i.e. a pass of the Execute() method) is guaranteed to stay on the same thread.

I'd like to implement tracing using the Ambient Context pattern to avoid passing the tracing service to any class that might want to trace. The problem is that as we know the plugin is only instantiated once per registered step and then serves all subsequent operations from the same instance; that means I can't just have some static property like Tracing.Current to which I assign the current ITracingService instance and I'm good to go. If I did that, the operation started last would overwrite the instance for all other operations that might still be running (and this sort of concurrency is not uncommon).

Now if I could be sure everything under the Execute() method remains in the same thread, I could still use an Ambient Context utilizing the [ThreadStatic] attribute for static fields:

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; }
    }
}

I would set this upon entering the Execute() method and clear it at the end so the reference to the tracing service instance will be removed.

The only thing I could kind of find out about threading in the context of MSCRM plugins is that apparently the individual threads come from the ThreadPool - whatever consequences that might have with regards to my issue.

Does anyone have a deeper insight into how threading is handled with MSCRM plugins - or any other ideas on how the cross-cutting concern of tracing could be handled elegantly with SOLID code in this special case (AOP/dynamic interception are no options here)?

Thanks for any help and pointers.

Elmiraelmo answered 14/12, 2012 at 20:46 Comment(2)
I understand your concern about running in a thread safe manner, but I'm not sure I understand why you're concerned about running on the same thread. Can you explain a little bit more?Mildamilde
Well, I'd need to avoid the plugin execution being moved to another thread without getting its own ITracingService instance "attached" again. I haven't been able to find out yet how the ThreadStatic thing would be handled in such a case.Elmiraelmo
P
3

The simple and smart-ass answer: if it hurts when you do that, then don't do that. :)

Your self-imposed requirement of using the Ambient Context pattern is conflicting with CRM's design pattern. Think of how CRM works - it passes you a IServiceProvider, with everything you need including the Tracing Service. It handles all the complicated multithreading and optimizations for you, and only asks that you don't try to outsmart it with fancy patterns or static variables or threading tricks.

My recommendation is to use the same pattern - pass the IServiceProvider to any classes or methods that need it. Much simpler - plus later when you have a weird bug, you'll not question whether you successfully outsmarted Microsoft's engineers or not. :)

Pangenesis answered 15/12, 2012 at 20:0 Comment(2)
I agree that one should always be wary of taking things too far or using systems in a way they were not designed to be used. I can't see how there would be any "complicated multithreading" involved with the tracing service, though. It'll either be there or it won't - and what I'm looking for is a way to make sure it is. ;-) By the way, the self-imposed requirement is actually good, maintainable software design. The Ambient Context is only a means to an end, and I'd be happy to do without it if possible.Elmiraelmo
Is it just so you can write Tracing.Current instead of writing the code to extract the Tracing service from the IServiceProvider every time? If so, maybe look at writing a C# extension method off of the ServiceProvider object that easily gets a reference to the Tracing service. Or use a code snippet that pastes that boilerplate code in for you - it won't likely change, and if it does, it is a simple find/replace. Above all, do not spend more than another 10 minutes on it and start solving real business problems, man! I'm glad I'm not paying you to worry about this! :)Pangenesis
M
1

CRM creates a single plugin object, and then uses threads as needed to process the requests. So the only thing you can be sure of is that you will have multiple threads running at a single time for a single plugin object.

The threads are managed through IIS, and will get reused if possible. So if you want to ensure that each time Execute is called, it has a new ITracingService you'll have to set it. If you just want to ensure that each time Execute is called, it has one, you'll just need to do an if statement to check for it.

Since your backing variable is ThreadStatic, you won't need to worry about threading issues, but since IIS tries to reuse threads, it will not be empty each time Execute is called.

Mildamilde answered 17/12, 2012 at 14:9 Comment(0)
M
0

I'm afraid I can only guess at the whole plugin/thread/static issue here, what you propose does seem a little complicated however. So as an alternative, have you considered using Trace Listeners?

If you use Trace.Writeline across your application then a single Trace Listener would capture all those messages. That way you don't have to pass a trace object around.

For example:

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!");
}

Relevant links:

Trace Listeners and Walkthrough: Creating a Custom Trace Listener

Mattins answered 16/12, 2012 at 11:31 Comment(2)
I have read about them, but dismissed them outright. I take it those are registered for the whole process/AppDomain, right? Each call to Execute() on the same plugin instance brings along its own ITracingService instance and must be guaranteed to use exactly and only that one instance. What I'm looking for is a way to ensure that without polluting most of my constructors with a parameter that isn't actually required for the respective class to do its work.Elmiraelmo
I don't know off the top of my head, I would have to dig around the MSDN. I understand your situation I've been there myself passing around logging objects all my functions and classes. I'll have a think about the process/AppDomain bit.Mattins

© 2022 - 2024 — McMap. All rights reserved.