Search code examples
c#multithreadingtask-parallel-librarytpl-dataflow

C# TPL: Invoke method on outer scoped instance


So my title was fairly obscure, here is what I'm worried about. Can I invoke a method on an instance of a class that is declared outside of the block without suffering pitfalls i.e

Are there concurrency issues for code as structured below.

HeavyLifter hl = new HeavyLifter();

var someActionBlock = new ActionBlock<string>(n =>
{
   int liftedStuff= hl.DoSomeHeavyLifting(n);
   if (liftedStuff> 0)
   .....
});

The source of my concerns are below.

  1. The Block may have multiple threads running at the same time, and each of these threads may enter the DoSomeHeavyLifting method. Does each function invocation get its own frame pointer? Should I make sure I don't reference any variables outside of the CountWords scope?

  2. Is there a better way to do this than to instantiate a HeavyLifter in my block?

Any help is greatly appreciated, I'm not too lost, but I know Concurrency is the King of latent errors and corner cases.


Solution

    1. Assuming by frame pointer, that you mean stack frame, then yes, each invocation gets it's own stack frame, and associated variables. If parameters to the function are reference types, then all of the parameters will refer to the same object.
    2. Whether or not it's safe to use the same HeavyLifter instance for all invocations depends on whether the DoSomeHeavyLifting method has side effects. That is, whether DoSomeHeavyLifting modifies any of the contents of the HeavyLifter object's state. (or any other referenced objects)

    Ultimately whether it is safe to do this depends largely on what DoSomeHeavyLifting does internally. If it's carefully constructed in order to be reentrant then there are no problems calling it the way you have it. If however, DoSomeHeavyLifting modifies the state, or the state is modified as a side effect of any other operation, then the decision would have to be made in the context of the overall architecture how to handle it. For example, do you allow the state change, and enforce atomicity, or do you prevent any state change that affects the operation? Without knowing what the method is actually doing, it's impossible to give any specific advice.

    In general when designing for concurrency it's usually best to assume the worst:

    • If a race condition can happen, it will.
    • When a race condition happens, you will lose the race in the most complex way your code allows.
    • Non-atomic state updates will corrupt each other, and leave your object in an undefined state.
    • If you use a lock there will be a case where you could deadlock.
    • Something that doesn't ever happen in debug, will always happen in release.