34

A while back I surprised to find that objects in Sitecore context are specific to the current thread. So, for example, if I run a parallel loop I can't rely on Sitecore context from inside that loop.

That got me thinking about my understanding of many the Sitecore features I use regularly. I've always been a slightly unclear on the thread scope of many of the 'context' type objects. You know the kind of thing:

  • DatabaseSwitcher
  • LanguageSwitcher
  • BulkUpdateContext
  • SecurityDisabler
  • EventDisabler
  • DatabaseCacheDisabler

Let's take the example of a bulk import. I know the advice is to disable as much of the stuff that might slow things down as possible. But in practice I'm always concerned about the side effects of doing so. Is it safe to use disablers etc. while other users are in the system or while jobs might be running? Or do they act only on the thread that created them?

If they are thread specific, then what advice is there for using them in a multi-threaded scenario? In a parallel loop, should I create the switchers/disablers inside each 'iteration'?

My head hurts...

1
  • 1
    Great question! We could maybe answer part of it by blocking the thread inside the switcher/disabler for a long time and running some experiments on a separate thread at the same time. Commented Oct 14, 2017 at 10:12

2 Answers 2

24
+50

In general they all seem to be using Sitecore.Context.Items which is a static Sitecore.Caching.ItemsContext object.

ItemsContext uses the curret HttpContext if it's available (making it request-scoped) or otherwise a private static IDictionary marked with the [ThreadStatic] attribute (making it thread-scoped).

public class ItemsContext : IDictionary, ICollection, IEnumerable
{
    [ThreadStatic]
    private static IDictionary dictionary;

    protected virtual IDictionary Dictionary
    {
        get
        {
            HttpContext current = HttpContext.Current;
            if (current != null)
                return current.Items;
            return ItemsContext.dictionary ?? (ItemsContext.dictionary = (IDictionary) new Hashtable());
        }
    }

    public object this[string key]
    {
        get { return this.Dictionary[(object) key]; }
        set { this.Dictionary[(object) key] = value; }
    }

    // ...
}

If they are thread specific, then what advice is there for using them in a multi-threaded scenario? In a parallel loop, should I create the switchers/disablers inside each 'iteration'?

When running things in parallel using Parallel.Foreach you would have to use the switcher/disabler inside the loop as they run on separate threads.

Parallel.ForEach(source, x =>
{
    using (new SecurityDisabler())
    {
        // Run your code here
    }
});

As Mark Cassidy also mentions in most cases I don't think there would be much or any gain in running stuff in parallel when accessing anything related to the Sitecore context as you would most likely be I/O limited anyway.

There might be a few usecases where it could make sense, but generally I probably would recommend against it.

Some more background info

Most of the switchers/disablers inherit from Sitecore.Common.Switcher<TValue, TSwitchType> class, which uses Sitecore.Context.Items to store the state (as a stack of the switched values, in case it's used multiple times).

Some other types uses a custom implementation and just inherits IDisposable.

Examples of types inheriting Switcher<TValue, TSwitchType>:

  • AuthenticationSwitcher
  • ContextItemSwitcher
  • EventDisabler
  • LinkDisbabler
  • LinkProviderSwitcher
  • LockingDisabler
  • SettingsSwitcher
  • TrackerSwitcher
  • SecurityDisabler
  • SiteContextSwitcher
  • And many more...

Examples of custom implementations only inheriting IDisposable:

  • BulkUpdateContext (Uses locking, internal state and also Sitecore.Context.Items)
  • LanguageSwitcher (Uses Sitecore.Context.SetLanguage())
4
  • So how should he use them, inside his parallel iterations?
    – Mark Cassidy
    Commented Oct 25, 2017 at 23:47
  • I think it's sort of implied although I guess it could be more clear. When running outside a request (which I suppose would be the case for parallelization) you'd have to apply them in each thread. I'm not sure how you'd parallelize it running in a request scope, but then you'd only need to do it once "outside" the parallelization I guess, depending on how you're actually doing it. Commented Oct 26, 2017 at 7:03
  • @SørenKruse I have to agree with Mark on this - while this has a lot of detail - can you add some extra around the OP's question - how can he do this in multi-threaded way or in a parallel loop should they be created there? SSE is all about actual rather than implied answers :) If you can make it clear, this would be a good answer
    – Richard Seal
    Commented Oct 26, 2017 at 14:08
  • 2
    I've updated the answer and also corrected the code for how it is actually handled by Sitecore (the old stuff was from Sitecore 6, the new stuff is from Sitecore 7.x) Commented Oct 26, 2017 at 15:42
9

Fine, I'll be the one to jump in and tell you what no one wants to hear ;-)

Sitecore.Context is an anti-pattern

I've argued this before, and I stand by it. The problems around this particular implementation pattern are many - personally I dislike it because it's more or less the exact opposite of Dependency Injection. And because of that, it makes achieving Inversion of Control all but impossible.

And that's what the Switchers (the examples above are all, in one form or another, based on a Switcher mechanic inside Sitecore) are. Mechanisms that attempt to give you (the caller of the API) back some control, against a rogue codebase that cherry picks whatever data it needs in Context.this and Context.that.

Now I understand why it was done this way. At least when I put on my 2003 glasses and the software landscape back then. Performance, lack of DI frameworks, overall maturity of the development practices and community.

But take these two examples (hand crafted, they may not compile):

public IEnumerable<INews> GetLatest3NewsArticles()
{
    // this could just as easily have been Sitecore.Context.NewsRepository
    var newsRepository = DependencyResolver.Resolve<INewsRepository>();

    var newsList = newsRepository.GetAll().Where(n => n.Language.Name == Sitecore.Context.Language.Name 
        && n.InnerItem.Axes.IsDescendantOf(Sitecore.Context.Site.StartItem).Take(3);
    return newsList;
}

And compare it to this:

public IEnumerable<INews> GetLatest3NewsArticles(INewsRepository newsRepository, Item siteRoot)
{
    var newsList = newsRepository.GetAll().Where(n => n.Language.Name == siteRoot.Language.Name
        && n.InnerItem.Axes.IsDescendantOf(siteRoot).Take(3);
    return newsList;
}

Now none of these are particularly good examples of code one would actually implement. But the key difference between them is; one uses Inversion of Control. It is controlled entirely by parameters that are sent to it. The other... well it's just sad. It can only be controlled from the outside, by controlling what dependency gets resolved, switching Context Language, switching Context Site.

And this is why you need Switchers. For Sitecore Code. For your own code, however, in my opinion there is no excuse. Your code should be more like example 2 above, and not like example 1.

So yes, the Switchers apply to current thread

Which is also why various multithreading scenarios are hard to do in Sitecore. Think Kam's experiments with async Controllers, as just one example. Or what you experience yourself now, trying to run migration code under Parallels.

But to come back to your question - the advise for using Switchers in a multi-threaded scenario? Don't. That's the advise. Design your code differently.

For this and any code you make, maintain Inversion of Control and don't write methods that rely on Sitecore.Context, Global Variables, or anything other than what they explicitly ask for in the method parameters. It's bad design and it's even worse encapsulation.

2
  • Thanks Mark. A very thorough answer and I get the general point. However, since I'm spunking 50 big ones on this, could you update your answer with some specific advice on disablers and contexts. I recall a blog post of yours recommending the use of BulkUpdateContext and would like to know your thoughts Commented Oct 25, 2017 at 21:38
  • This is true. However, your question aims directly at the use of these switchers in a multi threaded context, something I have never attempted nor do I see reason to. Running in a (properly tuned) migration scenario, you will be I/O bound - multiple threads will not overcome this. You asked for advise, I gave mine :-)
    – Mark Cassidy
    Commented Oct 25, 2017 at 22:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.