0

I have the following setup of TransformBlock:

private void SetupTestModule()
{
    Func<int, int> func1 = new Func<int, int>(input =>
        {
            return (input + 1);
        });

    Func<int, int> func2 = new Func<int, int>(input =>
        {
            return (input + 2);
        });

    TransformBlock<int, int> transform = new TransformBlock<int, int>(func1);
}

I wonder whether I can, during run-time, exchange func1 for func2 and whether all the linkages between this transformBlock and other data blocks will remain intact? Ideally upon swapping the Funcs I simply want the new transformation to apply to all newly incoming items. Obviously with this simplistic approach I would not make assumptions about items that are currently in the in-queue and how they are handled. I just wonder whether assigning a new Func would cause a runtime error or otherwise cause the transformBlock to be unlinked. Anyone who could share some insights?

Edit: Post Jon's suggestions and code here some very basic test code. What makes me curious is that it works with and without the volatile keyword. Why do we need volatile?

public class TransformBlockHotSwap
{
    private TransformBlock<int, int> transformBlock;
    private ActionBlock<int> actionBlock;

    public TransformBlockHotSwap()
    {

        SwappableFunction<int, int> swappable = new SwappableFunction<int, int>(item => item + 1);
        transformBlock = new TransformBlock<int, int>(item => swappable.Execute(item));
        actionBlock = new ActionBlock<int>(item => Console.WriteLine(item));
        transformBlock.LinkTo(actionBlock);

        Func<int, int> func2 = new Func<int,int>(item => item * item);

        for (int index = 1; index <= 100; index++)
        {
            transformBlock.Post(index);

            Thread.Sleep(500);

            if (index == 5)
            {
                swappable.Swap(func2);
            }
        }
    }
}

public class SwappableFunction<TInput, TOutput>
{
    private Func<TInput, TOutput> func;

    public SwappableFunction(Func<TInput, TOutput> func)
    {
        this.func = func;
    }

    public void Swap(Func<TInput, TOutput> newFunc)
    {
        func = newFunc;
    }

    public TOutput Execute(TInput input)
    {
        return func(input);
    }
}

Edit (to include predicate swapping):

public class TransformBlockHotSwap
{
    private TransformBlock<int, int> transformBlock;
    private ActionBlock<int> actionBlock;

    public TransformBlockHotSwap()
    {
        Func<int, int> defaultFunction = new Func<int, int>(item => item);
        Func<int, int> func2 = new Func<int, int>(item => item * item);
        Predicate<int> defaultPredicate = new Predicate<int>(item => true);
        Predicate<int> pred2 = new Predicate<int>(item =>
            {
                if (item % 2 == 0)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            });

        SwappableFunction<int, int> swappableFunction = new SwappableFunction<int, int>(defaultFunction);
        SwappablePredicate<int> swappablePredicate = new SwappablePredicate<int>(defaultPredicate);

        transformBlock = new TransformBlock<int, int>(item => swappableFunction.Execute(item));
        actionBlock = new ActionBlock<int>(item => Console.WriteLine(item));
        transformBlock.LinkTo(actionBlock, item => swappablePredicate.Execute(item));

        for (int index = 1; index <= 100; index++)
        {
            transformBlock.Post(index);

            if (index == 10)
            {
                swappablePredicate.Swap(pred2);
            }

            Thread.Sleep(200);
        }

        Console.WriteLine("Done");
        Console.ReadKey();
    }
}

public class SwappableFunction<TInput, TOutput>
{
    private volatile Func<TInput, TOutput> func;

    public SwappableFunction(Func<TInput, TOutput> defaultFunction)
    {
        this.func = defaultFunction;
    }

    public void Swap(Func<TInput, TOutput> newFunc)
    {
        func = newFunc;
    }

    public TOutput Execute(TInput input)
    {
        return func(input);
    }
}

public class SwappablePredicate<TInput>
{
    private volatile Predicate<TInput> predicate;

    public SwappablePredicate(Predicate<TInput> defaultPredicate)
    {
        this.predicate = defaultPredicate;
    }

    public void Swap(Predicate<TInput> newPredicate)
    {
        predicate = newPredicate;
    }

    public bool Execute(TInput input)
    {
        return predicate(input);
    }
}
Matt
  • 7,004
  • 11
  • 71
  • 117

1 Answers1

3

I wouldn't expect you to be able to do so - but you could easily write a function which delegates:

public class SwappableFunction<TInput, TOutput>
{
    private volatile Func<TInput, TOutput> func;

    public SwappableFunction(Func<TInput, TOutput> func)
    {
        this.func = func;
    }

    public void Swap(Func<TInput, TOutput> newFunc)
    {
        func = newFunc;
    }

    public TOutput Execute(TInput input)
    {
        return func(input);
    }
}

Then:

var swappable = new SwappableFunction<int, int>(input => input + 1);
var block = new TransformBlock<int, int>(swappable.Execute);
// Later...

swappable.Swap(input => input + 2);

Important - I'm not 100% sure about the use of volatile here - I generally don't like using volatile as its semantics are confusing. It's possible that using Interlocked.CompareExchange would be better - but the code would be significantly longer, and I wanted to get the main point across first :)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • would the same apply to `Predicate` as well? I use predicates as filters for data block message filtering. – Matt Feb 16 '13 at 09:44
  • I guess you meant to say `var block = new TransformBlock(input => swappable.Execute(input)); ` in the latter snippet? – Matt Feb 16 '13 at 09:48
  • 2
    @Freddy The compiler should quite happily convert `swappable.Execute` to a `Func`. Wrapping it in that lambda is effectively applying an unnecessary identity function. – anton.burger Feb 16 '13 at 11:50
  • @Freddy: You'd probably want to create a `SwappablePredicate` class - although a predicate is effectively a `Func` anyway. And shambulator is right - the method group conversion should be fine. – Jon Skeet Feb 16 '13 at 12:41
  • @JonSkeet, I guess I do not fully understand the usage of `volatile`, I read through couple references at various sites and often the conclusion was that it was at first recommended but then in the end unnecessary to use. In this case the solution fully works in the precisely same way each and every time even without usage of `volatile`. Why would we need `volatile` then? I added my code above. – Matt Feb 17 '13 at 05:01
  • @Freddy: It's entirely possible that you'll be calling `Swap` from one thread, but reading from the variable in another thread. Without *something* like volatile, there's no guarantee that the reading thread won't just keep using a value from a cache somewhere. – Jon Skeet Feb 17 '13 at 07:48
  • @JonSkeet, got it, would you know whether the process within a data flow block is always guaranteed to run in the same thread context as the context which passes the data to it through `Post` or `SendAsync`? (Given I use the default TaskScheduler) – Matt Feb 17 '13 at 08:03
  • @Freddy: I'd hope not with `SendAsync` at least! (I'm not *sure* about `Post`) If the idea is to separate consumers from producers, I'd expect them to use different contexts. But I haven't looked at Dataflow much - do check the documentation. – Jon Skeet Feb 17 '13 at 08:04
  • @JonSkeet, sorry last question, if I may. I do not seem to be able to work out things if I instead use predicates. Do you mind taking a look at my last code snippet which I added above? As soon as the predicate is swapped and as soon as the new predicate returns false for the first time the predicate seems to always return false. – Matt Feb 17 '13 at 08:12
  • @Freddy: I don't know, to be honest. I'll try to have a look later on, but I don't have time right now. It looks okay to me. – Jon Skeet Feb 17 '13 at 08:18
  • @JonSkeet, thanks a lot for your help. I noticed my error myself: A predicate within `LinkTo` returning false will clog up the pipeline and needs to be handled (for example by linking to another data block which handles the filtered-out items or to `DataflowBlock.NullTarget`) – Matt Feb 17 '13 at 08:26
  • @Freddy: Fair enough - that's my lack of experience with Dataflow coming out then :) – Jon Skeet Feb 17 '13 at 08:32