views:

333

answers:

4

I am trying to implement the following functionality:

class WeightResolver {
  WeightMonitor _source;
  bool _cancelled;
  Weight _threshold;

  public Cancel() {
      _cancelled = true;
  }
  public Weight Resolve(){
      _cancelled = false;
      while(_source.CurrentWeight < threshold ) {
         if(_cancelled)
             throw new CancelledOperationException();
         // Wait until one of the above conditions is met
      }
      return _source.CurrentWeight
  }
}

However I am running into trouble managing my threads. For example, the Cancel method is registered via an event and Resolve invoked as follows:

  _activity_timeout_manager.TimeoutHandler += new Action(_weight_resolver.Cancel())l
  try {
      var weight = _weight_resolver.Resolve();
  }
  catch(CancelledOperationException) { .... }

where the activity manager is running a timer on tick of which it invokes events using TimeoutHandler.Invoke();

The problem is that even though it is properly registered with the event, Cancel() never gets called. I believe this is because the thread it is calling to is currently spinning and therefore it never gets a chance at the CPU.

What can I do to remedy the situation short of making the call to Resolve() asynchronous? It is extremely preferable for WeightResolver.Resolve() to stay synchronous because the code calling it should spin unless some return is provided anyways.

EDIT: To clarify what I'm asking for. This seems like a fairly common set-up and I would be surprised if there isn't a simple standard way to handle it. I simply have never run across the situation before and don't know what exactly it could be.

+1  A: 

This might not work for you, but based on the information you've provided, I'd suggest looking at thread.Join(XXX) where XXX is the number of milliseconds to wait. It'll greatly simplify your code.

http://msdn.microsoft.com/en-us/library/6b1kkss0.aspx

you can block the calling thread to the new thread for a specified amount of time, after which you can abort the Resolve thread.

resolveThread.Start();
resolveThread.Join(2000); //this will block the main thread, thus making resolve synchronous
resolveThread.Abort(); //timeout has expired
Karl Seguin
Karl, it is very possible that I'm missing something but I don't get how this would work for me where My WeightResolver object is being notified to cancel via an event
George Mauer
It might not work, but using this approach you'd remove the event and the timer (removing code is always good!). Instead you'd rely on the timeout functionality of the Join method to cancel the thread. From what you showed, your event+timer is nothing more than a timeout mechanism.
Karl Seguin
It is but it affects more components than just this one which have to react to the same event.
George Mauer
A: 

Is this while loop ever giving up the CPU?

while(_source.CurrentWeight < threshold )

If not, then your inactivity timer won't get a chance to run. You might want to use ManualResetEvents (instead of the loop... have whatever sets _source.CurrentWeight set the event) or throw in a Thread.yield() every once in a while.

bobwienholt
Right, that is exactly the problem, what is thread.yield? Its not a method on the Thread class? At least not in 3.5.
George Mauer
It's Thread.Sleep(). Even sleeping for zero milliseconds will at least give up the rest of your thread's timeslice, but something like 100 milliseconds is probably better.
Brannon
I've tried this, unfortunately it does not work.
George Mauer
A: 

What you need here are condition variables. I don't know specifically for .NET but in general you will have something like this

Condition cond;
Mutex lock;

public Cancel() {
      lock.lock()
      _cancelled = true;
      cond.signal(lock);
      lock.unlock();
  }
  public Weight Resolve(){
      _cancelled = false;
      lock.lock();
      while(_source.CurrentWeight < threshold) {
         if(_cancelled)
          {
             lock.unlock();
             throw new CancelledOperationException();
           }
          cond.timedWait(lock, 100);
         // Wait until one of the above conditions is met
      }
      lock.unlock();
      return _source.CurrentWeight
  }

even better would be if your WeightResolver signalled on the same condition when the weight changed. e.g.

Condition cond;
Mutex lock;

public Cancel() {
      lock.lock()
      _cancelled = true;
      cond.signal(lock);
      lock.unlock();
  }
  public Weight Resolve(){
      _cancelled = false;
      lock.lock();
      while(_source.CurrentWeight < threshold) {
         if(_cancelled)
          {
             lock.unlock();
             throw new CancelledOperationException();
           }
          cond.wait(lock);
         // Wait until one of the above conditions is met
      }
      lock.unlock();
      return _source.CurrentWeight
  }

and in the WeightMonitor Class, you had something like this.

public void updateWeight()
{
    lock.lock();
    ...update weight;
    cond.signal(lock);
    lock.unlock();
}

where the Conditionvariable and lock are the same. in both classes.

This is a pretty standard use for condition variables, this is also how join is generally implemented.

luke
What language is this in? I don't know of anything analogous to this Condition type in .NET.
George Mauer
A: 

You should be using a ManualResetEvent instead of a bool for canceling.

class WeightResolver {
  WeightMonitor _source;
  ManualResetEvent _cancelled = new ManualResetEvent(false);
  Weight _threshold;

  public Cancel() {
      _cancelled.Set();
  }
  public Weight Resolve(){
      _cancelled = false;
      while(_source.CurrentWeight < threshold ) {
         if(_cancelled.WaitOne(100))
             throw new CancelledOperationException();
         // Wait until one of the above conditions is met
      }
      return _source.CurrentWeight
  }
}
Brannon
Interesting, didn't know about that.
George Mauer
Unfortunately, this still doesn't work since the Cancel() method never gets called. My best guess is that Invoke() on an event fires it on the thread it originated from so it is still being blocked by the ManualResetEvents
George Mauer