tags:

views:

230

answers:

6

Hello guys, I am new here...

I have one question, if somebody can help me.

It is about timers (System.Threading.Timer).

I want to break inevitable recursion: I have two columns in datarow and they are mutually dependant (price_without_VAT and price_with_VAT). Setting one of them will definitely cause StackOverflowException. So here's the idea:

bool flag = true;
void Reset(object state) { flag = true; }

Now, wrap the method for changing value of one of the columns:

{
    if(flag)
    {
        flag = false;
        System.Threading.Timer tmr = new System.Threading.Timer(new System.Threading.TimerCallback(Reset), null, 10, System.Threading.Timeout.Infinite);
        datarow.other_column = value;
    }
}

datarow.other_column.value line will immediately trigger the above method, but there will be no recursion because flag is false. In 10 ms flag should be back to true, and everything is back to normal.

Now, when i follow the code in DEBUGGER, everything works fine, but when I start app NORMALLY Reset function simply will not trigger, flag is stuck to false forever and everything false apart. I play around with due_time parameter but nothing seems to help.

Any ideas?

A: 

I wouldn't do this. You're going to run into timing issues. What happens if you attempt to set both the "flagged" values in quick succession? This will at best lead to weird debugging, at worst lead to invalid data being saved.

You could make the flag global, set it in one property, and check it in the other. Set the flag, change the value, and then set the flag back when you're done. Or, make the getters do the underlying calculation, and don't allow one of the values to be explicitly set.

jvenema
+1  A: 

sounds like you have all sorts of nasty races going on here. YOu really need to fix your underlying problem

pm100
+2  A: 

It seems the real problem you have is the StackOverflow exception due to non-terminating recursion - you should fix that and then there is no need to use Timers like this.

Lee
No, StackOverflow is inevitable. Users can enter price with VAT or price without VAT, whatever they choose. My job is to set the other one, and, ofcourse, to break the recursion.
Mikelly
Wrap the column that the user is setting in a Property, when that property is set, automatically set the other one from inside that Property. It doesn't sound like this requires timers.
s_hewitt
@mijatovic - I doubt it is inevitable - however you plan on preventing it using a timer you could do synchronously.
Lee
Price_with_VAT = Price_no_VAT * (100 + VAT) / 100;Price_no_VAT = 100 * Price_with_VAT / (100 + VAT);Price_with_VAT and Price_no_VAT free for user to enter.I cannot avoid recursion, I can only limit it to only two iterations. Please correct me if I am wrong.
Mikelly
A: 

I wouldn't use timers here. What I usually do when I have this kind of problem (which is not so often), is something along these lines:

bool _isSettingAOrB;
private int _a;
private void SetA(int value)
{
    _a = value;

    if (_isSettingAOrB)
    {
        return;
    }

    _isSettingAOrB = true;

    try
    {
        SetB(_a - 10);
    }
    finally
    {
        _isSettingAOrB = false;
    }
}

private int _b;
private void SetB(int value)
{
    _b = value;

    if (_isSettingAOrB)
    {
        return;
    }

    _isSettingAOrB = true;

    try
    {

        SetA(_b + 10);
    }
    finally
    {
        _isSettingAOrB = false;
    }
}

If you don't like repeating patterns (as in the code above), you can wrap the call structure into a separate method instead:

bool _isSettingAOrB;
private int _a;
public void SetA(int value)
{
    SetInterdependentValues(() => _a = value, () => SetB(_a - 10));
}

private int _b;
public void SetB(int value)
{
    SetInterdependentValues(() => _b = value, () => SetA(_b + 10));
}

private void SetInterdependentValues(Action primary, Action secondary)
{
    primary();

    if (_isSettingAOrB)
    {
        return;
    }
    _isSettingAOrB = true;
    try
    {
        secondary();
    }
    finally
    {
        _isSettingAOrB = false;
    }
}

Brief explanation of the code:

SetInterdependentValues(() => _a = value, () => SetB(_a - 10));

It's a method call that takes two parameters. The two parameters are () => _a = value and () => SetB(_a - 10). In short, these are lambda expressions that will be turned into delegates, where the body of each method is what is located to the right of =>. So the first argument is a method that assigns value to _a, and the second argument will call the method SetB, passing the argument _a - 10.

The SetInterdependentValues will execute the first method, but execute the second method only if _isSettingAOrB is false. However, it will set _isSettingAOrB to true before making the call. This will prevent the infinite recursion from happening. This last part is done in a try-finally block in order to guarantee that the flag is reset also if the invoked method throws an exception.

Fredrik Mörk
this sounds nice... but, you are passing functions as parameters this way?But, I still don't know if it would help. My changes occur in datacolumnchanged event of datarow to which i am subscribed, which means that every time i change a value the same event will fire again and forever if i don't break it. I could unsubscribe just before the change and subscribe after but that would mean that the other value will not be, so to say, 'updated'.Do you think i could accomplish what i need with your code. To be honest i need some time to fully understand your code.
Mikelly
@mijatovic: I can't guarantee how the DataColumnChanged event behaves (didn't use it a lot), but typically event handlers are called synchronously (they execute on the same thread) so I think it would work out well. As for how the code works, I'll update the answer with some explanations.
Fredrik Mörk
I see now. Nice stuff. Though, for it to work in my case, I must definitely unsubscribe from ColumnChanged event before changing either value, to leave them 'free' for changes, and after that, subscribe again. It's like making those two changes atomic. Thanks. Very helpful.
Mikelly
@mijatovic: I am not so sure you would need to unsubscribe. I would believe it would work like this columnAchanged -> SetA -> SetB -> columnBchanged -> SetB. Note that the last call to SetB will simply update the field, but not call SetA (because _isSettingAOrB is true). So the stack would unfold from there and everybody are happy. Worth a try at least, I would say.
Fredrik Mörk
Whatever function is last to be called it would 'touch' column value in datarow object. That 'touch' would then trigger two events: first ColumnChanging and then ColumnChanged event. There is NO WAY OUT of this except unsubscribing. Or, the way i figured, modify the handler itself not to 'touch' the value, thus breaking the recursion. I want the recursion to last two iterations only, to change both values and then break in third. Don't know if you understand me.
Mikelly
+1  A: 

Use lock instead of the flag to ensure that the update happens in only one thread at a time.

// class member
private object syncObject = new object();

// then, in your code...
lock(syncObject) {
   System.Threading.Timer tmr = new System.Threading.Timer(new System.Threading.TimerCallback(Reset), null, 10, System.Threading.Timeout.Infinite);
   datarow.other_column = value;
}
spoulson
A: 

Whereas I would agree with those who say that you should find another way to prevent the infinite recursion, the reason your timer doesn't fire is probably because it's being optimized away. I ran into this recently doing something else.

Let's say you want to have a periodic timer:

void SomeMethod()
{
    Timer MyTimer = new Timer(MyTimerProc, null, 3000, 3000);
    // other stuff goes here
}

Now, you run that in debug mode and everything works. But when you run it in release mode, the timer never fires. It's because it's being optimized away. What you need to do is either keep it alive (with a GC.KeepAlive) or a using:

using (Timer MyTimer = new Timer(MyTimerProc, null, 3000, 3000))
{
    // other stuff here
}
Jim Mischel
You are right. I declared the timer as a member of a form so it never runs out of scope, and now everything works fine. Using 'using' was not helpful though. Thanks for solution.
Mikelly