views:

190

answers:

4

I have code running in a background thread that need to periodically check if the user wants to abort the work.

The question is how to implement this in a mostly SOLID environment.

  • An ICancel interface that I inject into every class.
  • A public static member somewhere

Non-const statics seem to always create problems sooner or later. On the other hand the number of "standard" injections is steadily growing (ILogger, IProgressReporter, ...) so a simple thing like cancel might be good candidate to use static.

Are there other/better ways? Anyone got experience to share?

I'm using WPF and C# but the question is general.


Here is an example:

// this code is in some model assembly

public class BackgroundWorkFactory {
    public IDoingBackgroundWork Worker { 
        get { return new DoingBackgroundWork(new Whatever()); }
    }

internal class DoingBackgroundWork : IDoingBackgroundWork {
    public DoingWork(IWhatever whatever) {
        mWhatever = whatever;
    }
    public void WorkThatCanBeCanceled() {
        while (!Canceled && somethingElse) {
            mWhatever = whatever.DoSomthingElseThatMightAlsoAllowCancel();
            ...
        }
    }
}


// This code is in the GUI Assembly

public void StartWork() {
    IDoingBackgroundWork work = factory.Worker;
    Thread t = new Thread(work->WorkThatCanBeCanceled());
    t.Start();
}

public void StopWork() {
   // ??
}
A: 

Well you could also simply make a volatile flag in each class called UserHasWork which will run whilst true. If the user cancels, check the flag and stop the thread.

This cannot however be used in an interface, however I do find it a useful way of performing what you want to. You could always use it in an abstract class too, but the cons outweigh the pros with that approach.

Kyle Rozendo
A: 

IIUC, you are proposing that the static means to cancel all background threads. I feel that this is not modular enough, and that each thread should be cancellable individually.

OTOH, an ICancel interface alone doesn't allow much reuse. I would propose a class

class CancelThread{
  private boolean cancelled;
  synchronized void cancel(){cancelled = true;}
  synchronized boolean isCancelled(){return cancelled}
}

Each thread would have one of these, and perhaps there is also a global set of many such objects. So to cancel all threads, you iterate over the set and invoke cancel for every one.

Martin v. Löwis
Where should I put the CancelThread object?How does my distant DoingWork class in another assembly access it?(static dictionary with threadid as key)?
adrianm
DoingWork would create an instance of it - IOW, it would need to link with the assembly defining it.
Martin v. Löwis
The GUI code knows nothing about any DoingWork instances (they could be created in an IoC framwork). How will the GUI access the CancelThread object. I'll edit the question to explain.
adrianm
See my proposal: collect all CancelThread objects in a global set, which the GUI would iterate over.
Martin v. Löwis
ok, now I get it. Your suggestion is to use the static solution. Good idea to use one cancel per thread.
adrianm
A: 

Maybe I'm a bit lost here but why can't the IDoingBackgroundWork declare a simple Cancel method which will in turn be implemented in the base DoingBackgroundWorkerBase class?

That way you'll have a simple way to cancel a worker thread from the caller, a default implementation and the ability to modify that implementation. Is it really necessary to use injection for this? I think old good "simple" OO patterns cover it right.

Update:

Then I don't like either approach. IWhatever would need to know he must stop gracefully at some time so you can't just inject code and expect it to exit gratefully and a static class will add the same level of coupling (meaning IWhatever must know and use that static class).

I'd go for defining a ICancelable interface and then doing:

class DoingBackgroundWorkBase
{
  public Cancel()
  {
    ICancelable cancelableWork = mWhatever as ICancelable;
    if (cancelableWork != null)
      cancelableWork.Cancel();
    else
      this.Abort();
  }  
}

and of course

IDoingBackgroundWork work = factory.Worker;
work.Start();

so that the IDoingBackgroundWork is in charge of manually starting the thread (or possibly being the thread itself) and providing a "graceful for the ungraceful" kind of exit for when IWhatever doesn't implement ICancelable as well.

Jorge Córdoba
Problem is that work isn't only done within the `DoingBackgroundWork` class. Part of the work is done in other classes (`Whatever` in the example) which the GUI don't know anything about. So the Cancel method need to call Cancel on all dependent classes as well and that's even more work than injecting ICancel
adrianm
Ok, I see, I've updated the response then but I still don't see either of the proposals ...
Jorge Córdoba
That doesn't scale very well for 2 reasons: * It will not work if another dependency is added. The `Cancel` method can't know in which dependency the thread is currently executing.* The `Whatever` class might not implement `ICancelable` but one its dependencies might.
adrianm
A: 

After trying a few things I solved it like this.

Created a StopRequest class (don't see the need for an interface)

public class StopRequest
{
    public void RequestStop() {
        mIsStopRequested = true;
    }

    public void Reset() {
        mIsStopRequested = false;
    }

    public static implicit operator bool(StopRequest stopRequest) {
        return stopRequest.mIsStopRequested;
    }

    private volatile bool mIsStopRequested;
}

Injected this class to each class that needs it (or pass it as method argument)

public void StartWork() {
    mStopRequest = new StopRequest();
    IDoingBackgroundWork work = factory.Worker(mRequestStop);
    mThread = new Thread(work->WorkThatCanBeCanceled());
    mThread.Start();
}

public void StopWork() {
    mStopRequest.RequestStop();
    mThread.Join(timeout);
}

//-----

public class BackgroundWorkFactory {
    public IDoingBackgroundWork Worker(StopRequest stopRequest) { 
        return new DoingBackgroundWork(stopRequest, new Whatever(stopRequest));
    }
}

internal class DoingBackgroundWork : IDoingBackgroundWork {
    public DoingBackgroundWork(StopRequest stopRequest, IWhatever whatever) {
        mStopRequest = stopRequest;
        mWhatever = whatever;
    }

    public void WorkThatCanBeCanceled() {
        while (!mStopRequest && somethingElse) {
            x = mWhatever.DoSomthingElseThatMightAlsoAllowCancel();
            ...
        }
    }
}

This has the following benefits
- No need for static/singleton objects
- Only classes that has cancel functionality are affected
- A DI framework can probably inject it automatically
- No complicated setup when unit testing
- Lightweight/fast

adrianm