2

Is there a way to shorten my BackgroundWorker.CancellationPending checkpoint?

For example, is there a way to encapsulate return like the example code below?:

//REAL CODE (CURRENTLY USE THIS)
if (this.TW.CancellationPending) 
    return; 

//PSEUDO REPLACEMENT CODE
this.CkPt(CurrentMethod); //PSEUDO USAGE 
    //^^^ PARAMETER IS A REFERENCE TO THE CURRENT METHOD, SIMILAR TO `this` FOR AN OBJECT  
//OR MAYBE AN EXTENSION METHOD WOULD LOOK CLEANER
CurrentMethod.CkPt(); //PSEUDO USAGE

private void CkPt(Method m) //PSEUDO METHOD
{
    /*
        POSSIBLY PERFORM OTHER CHECKPOINT TASKS HERE
    */
    if (this.TW.CancellationPending) 
        m.return/*FROM METHOD THAT CALLED ME*/;
}  

I'm trying to make multi-checkpoint situations like this more readable:

//PSUEDO METHOD 
//DO NOT TAKE THIS AS REPEATING CODE
//IT IS ONLY MEANT TO SHOW MULTIPLE USES OF THE SAME CHECKPOINT
//MY REAL TASK METHOD(S) CONTAIN MANY MANY MANY AREAS THAT DON'T REPEAT AND REQUIRE CHECKPOINTS  
public void FakeBWTask()
{
    if (this.TW.CancellationPending) 
        return; 

    foreach (var F1 in Fake1)
    {
        if (this.TW.CancellationPending) 
            return; 

        foreach (var F2 in Fake2)
        {   
            if (this.TW.CancellationPending) 
                return; 
            foreach (var F3 in Fake3)
            {
                if (this.TW.CancellationPending) 
                    return; 
            }
        }
    }
}

Thanks for any help!!

PiZzL3
  • 2,092
  • 4
  • 22
  • 30
  • Hmya, awkward code of course. Injecting an exception into the thread is everybody's favorite bugaboo. Does it *really* matter that the thread cancellation is effective at anything less than human perception time? 50 milliseconds is a hellofalot of cpu cycles. – Hans Passant Aug 04 '11 at 00:04
  • It's to stop events from occurring and only an example. In my real code, the events are much longer than 50 ms. I really shouldn't have put the that second code in there. It's ruining the question. – PiZzL3 Aug 04 '11 at 00:24

2 Answers2

5

There is no way call for methodA to call methodB and let methodB return methodA (since we don't have tail recursion)

Consider using an iterator like this. It will work in some situations where you can put try/catch in-between checkpoints.

public void FakeBWTask()
{
   if (this.TW.CancellationPending) 
      return; 
   foreach (object ignore in FakeBWTaskSteps())
   {
      // Other checkpoint logic here....
      if (this.TW.CancellationPending) 
          return; 
   }
}

private IEnumerable<object> FakeBWTaskSteps()
{
   Part1();
   yield return null; // Execute checkpoint logic.

   Part2();
   yield return null; // Execute checkpoint logic.

   Part3();
   yield return null; // Execute checkpoint logic.

   Part4();
   yield return null; // Execute checkpoint logic.

   // Do some other stuff.
   yield return null; // Execute checkpoint logic.

   // final stuff.  No looping here.
}
agent-j
  • 27,335
  • 5
  • 52
  • 79
  • Sorry for not being clear in my example code. I just quickly wrote some repeating code that wasn't intended to be taken as repeated. Iterating through it is not what I'm looking for and doesn't answer my question. My REAL code is not repeating at all and does contain multiple checkpoints. I'll update my example above. – PiZzL3 Aug 03 '11 at 23:14
  • @PiZzL3, this will work if the checkpoint logic is the same each time. IF the checkpoint logic is different, then return a delegate that can execute the logic. – agent-j Aug 03 '11 at 23:16
  • @PiZzLe3, I've updated my answer, but I still don't see why it misses the mark. – agent-j Aug 03 '11 at 23:22
  • the checkpoint logic is the same(`if (this.TW.CancellationPending) return;`), but the code around it is not. There's no repeating. So I don't see how this would work at all. I'm looking for a way to encapsulate the `return` statement in a method and have it cause a `return` to be called from the calling method if conditions are met. – PiZzL3 Aug 03 '11 at 23:23
  • it misses the mark because from what I understand of your example, you're assuming that my code repeats, when it doesn't. I gotta go google this `yield` term to see how much that changes your code. – PiZzL3 Aug 03 '11 at 23:26
  • 1
    Since there is no way call for methodA to call methodB and let methodB return methodA (since we don't have tail recursion), you might consider my solution. The FakeBWTaskSteps effectively returns at every checkpoint, but the FaskeBWTask method resumes FakeBWTaskSteps where it left off unless cancelation is pending. Step through it in a debugger and it will be clear. – agent-j Aug 03 '11 at 23:28
  • "Since there is no way call for methodA to call methodB and let methodB return methodA (since we don't have tail recursion), you might consider my solution." <- Thanks, that's my answer right there. You're main answer above is very situation specific. If you add this to the top of your current answer, I'll accept it. – PiZzL3 Aug 03 '11 at 23:33
1

You could use mono.Cecil to do IL rewriting. You could decorate a method with a "Cancellable" attribute, and then rewrite any methods with that attribute. You would need to build a CFG from the instruction stream, write some code to compute stack depths, identify places where the stack depth is zero, inject nodes to do the cancel check, and then reserialize the CFG into a method body. It would probably take about a week, plus time to test everything. You'd also have to add a post build step.

Generally, unless you have a lot of cancelable methods, its probably not worth while.

Scott Wisniewski
  • 24,561
  • 8
  • 60
  • 89
  • Wow, I've seen cool stuff like that (tricks with IL code), but it's WAYYYYYY over my head right now. I'm just starting to scrape the surface of the more advanced C# features. – PiZzL3 Aug 04 '11 at 01:49