Отменить шаблон для фоновой нити - PullRequest
1 голос
/ 28 августа 2009

У меня есть код, работающий в фоновом потоке, который должен периодически проверять, хочет ли пользователь прервать работу.

Вопрос в том, как реализовать это в основном в ТВЕРДОМ окружении.

  • ICancel интерфейс, который я внедряю в каждый класс.
  • Открытый статический член где-то

Неконстантные статики, похоже, всегда создают проблемы рано или поздно. С другой стороны, число «стандартных» инъекций неуклонно растет (ILogger, IProgressReporter, ...), поэтому простая вещь, такая как отмена, может быть хорошим кандидатом для использования статических.

Есть ли другие / лучшие способы? У кого-нибудь есть опыт, которым можно поделиться?

Я использую WPF и C #, но вопрос общий.


Вот пример:

// 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() {
   // ??
}

Ответы [ 4 ]

0 голосов
/ 15 сентября 2009

После нескольких попыток я решил это следующим образом.

Создал класс StopRequest (не вижу необходимости в интерфейсе)

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;
}

Внедрил этот класс в каждый класскоторый нуждается в этом (или передает его в качестве аргумента метода)

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();
            ...
        }
    }
}

Это имеет следующие преимущества
- Нет необходимости в static /одноэлементные объекты
- затрагиваются только классы, которые имеют функцию отмены
- структура DI, вероятно, может внедрить его автоматически
- нет сложных настроек при модульном тестировании
- легкий / быстрый

0 голосов
/ 28 августа 2009

IIUC, вы предлагаете, что static означает отмену всех фоновых потоков. Я чувствую, что это не достаточно модульно, и что каждый поток должен быть отменен индивидуально.

OTOH, интерфейс ICancel сам по себе не позволяет многократно использовать. Я бы предложил класс

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

Каждый поток может иметь один из них, и, возможно, существует также глобальный набор многих таких объектов. Таким образом, чтобы отменить все потоки, вы перебираете множество и вызываете отмену для каждого.

0 голосов
/ 28 августа 2009

Может быть, я немного растерялся, но почему IDoingBackgroundWork не может объявить простой метод Cancel, который, в свою очередь, будет реализован в базовом классе DoingBackgroundWorkerBase?

Таким образом, у вас будет простой способ отменить рабочий поток из вызывающей стороны, реализация по умолчанию и возможность изменить эту реализацию. Действительно ли для этого нужно использовать инъекцию? Я думаю, что старые добрые "простые" ОО-модели это правильно.

Обновление:

Тогда мне не нравится ни один подход. I Что бы ни понадобилось знать, он должен изящно остановиться в какое-то время, так что вы не можете просто вставить код и ожидать, что он выйдет с благодарностью, и статический класс добавит тот же уровень связывания (то есть I, что должно знать и использовать этот статический класс).

Я бы определил интерфейс ICancelable, а затем выполнил:

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

и, конечно,

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

так, чтобы IDoingBackgroundWork отвечал за ручной запуск потока (или, возможно, сам поток) и предоставление «изящного за неблагодарный» выход для случая, когда IWhущество также не реализует ICancelable.

0 голосов
/ 28 августа 2009

Ну, вы также можете просто сделать флаг volatile в каждом классе с именем UserHasWork, который будет работать пока true. Если пользователь отменяет, проверьте флаг и остановите поток.

Однако это нельзя использовать в интерфейсе, однако я считаю, что это полезный способ выполнить то, что вы хотите. Вы всегда можете использовать его и в абстрактном классе, но плюсы при таком подходе перевешивают плюсы.

...