2010-06-10 3 views
4

У меня есть класс, который является классом «менеджера». Одна из его функций - сигнализировать о том, что длительный процесс класса должен быть отключен. Он делает это, устанавливая логическое имя «IsStopping» в классе.Как отменить глубоко вложенный процесс

public class Foo 
{ 
    bool isStoping 

    void DoWork() { 
     while (!isStopping) 
     { 
      // do work... 
     } 
    } 
} 

Теперь DoWork() была гигантская функция, и я решил реорганизовать это и как часть процесса сломал некоторые из них в другие классы. Проблема в том, что некоторые из этих классов также имеют длинные функции, которые необходимо проверить, является ли isStopping истинным.

public class Foo 
{ 
    bool isStoping 

    void DoWork() { 
     while (!isStopping) 
     { 
      MoreWork mw = new MoreWork() 
      mw.DoMoreWork() // possibly long running 
      // do work... 
     } 
    } 
} 

Какие у меня варианты?

Я рассмотрел прохождение isStopping по ссылке, что мне не очень нравится, потому что для этого требуется внешний объект. Я бы предпочел сделать дополнительные классы как самостоятельными, так и свободными как можно более свободными.

Я также рассмотрел возможность создания isStopping свойства, а затем, когда он вызывает событие, на которое могут быть подписаны внутренние классы, но это кажется слишком сложным.

Другим вариантом было создать класс «Токмен процесса отмены», аналогичный тому, что использует .net 4 Tasks, затем этот токен передается этим классам.

Как вы справились с этой ситуацией?

EDIT:

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

EDIT2:

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

+0

Использование потоков или без потоков? Потому что ИМО, если что-то подобное не многопоточно, трудно увидеть сценарий. – Marcote

+0

Нитки или нет в значительной степени неактуальны. Он может быть установлен с помощью сообщения Windows, для isntance, в однопоточном приложении, в котором есть сообщение. Однако да, моя конкретная реализация использует потоки. –

ответ

5

Вы можете пойти двумя путями здесь:

1) Решение Вы уже указано: передать сигнальный механизм для ваших подчиненных объектов: логическое значение (по реф), родительский объект сам облачена в интерфейсе (Foo: IController в примере ниже) или что-то еще. Детские объекты проверяют сигнал по мере необходимости.

// Either in the MoreWork constructor 
public MoreWork(IController controller) { 
    this.controller = controller; 
} 

// Or in DoMoreWork, depending on your preferences 
public void DoMoreWork(IController controller) { 
    do { 
     // More work here 
    } while (!controller.IsStopping); 
} 

2) Поверните его вокруг и использовать observer pattern - который позволит вам отделить ваши подчиненные объекты от родителя. Если бы я делал это вручную (вместо того, чтобы использовать событие), я бы изменить мои подчиненные классы для реализации интерфейса IStoppable, и сделать мой менеджер класс сказать им, когда остановиться:

public interface IStoppable { 
    void Stop(); 
} 

public class MoreWork: IStoppable { 
    bool isStopping = false; 
    public void Stop() { isStopping = true; } 
    public void DoMoreWork() { 
     do { 
      // More work here 
     } while (!isStopping); 
    } 
} 

Foo поддерживает список его stoppables и в своем собственном методе остановки, останавливает их все:

public void Stop() { 
    this.isStopping = true; 
    foreach(IStoppable stoppable in stoppables) { 
     stoppable.Stop(); 
    } 
} 
0

Я думаю, что уволил событие, которое подписи вашего подкласса имеет смысл.

0

Вы можете создать метод Cancel() в своем классе менеджера и на каждом из ваших других рабочих классов. Настройте его на интерфейс.

Класс менеджера или классы, которые создают экземпляры других классов работников, должны будут распространять вызов Cancel() на объекты, из которых они состоят.

Самые глубокие вложенные классы тогда просто установили бы внутренний _isStopping bool в false, и ваши долговременные задачи будут проверять это.

В качестве альтернативы вы могли бы создать какой-то контекст, о котором знают все классы, и где они могут проверить отмененный флаг.

Другой вариант заключается в создании «Process Отмены токен» класс, аналогично тому, что .net 4 Задачи использовать, а затем этот маркер будет передан в эти классы.

Я не знаком с этим, но если это в основном объект с флагом свойства bool и что вы проходите в каждый класс, то это кажется мне самым чистым способом. Затем вы можете создать абстрактный базовый класс, в котором есть конструктор, который принимает это значение, и устанавливает его в частную переменную-член. Тогда ваши циклы процесса могут просто проверить, что для отмены. Очевидно, вам нужно будет сохранить ссылку на этот объект, который вы передали своим работникам, чтобы на нем можно было установить флаг bool из вашего пользовательского интерфейса.

0

помет ваш код с утверждениями, как это везде, где это наиболее целесообразно проверить стоп флаг:

if(isStopping) { throw new OperationCanceledException(); } 

Catch OperationCanceledException прямо на верхнем уровне.

Для этого нет реального штрафа за исполнение, потому что (а) это произойдет не очень часто, и (б) когда это произойдет, это произойдет только один раз.

Этот метод также хорошо работает вместе с компонентом WinForms BackgroundWorker. Работник автоматически поймает заброшенное исключение в рабочий поток и отправит его обратно в поток пользовательского интерфейса. Вы просто должны проверить тип e.Error собственности, например .:

private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { 
    if(e.Error == null) { 
     // Finished 
    } else if(e.Error is OperationCanceledException) { 
     // Cancelled 
    } else { 
     // Genuine error - maybe display some UI? 
    } 
} 
+0

Вы можете повторно использовать 'OperationCanceledException' вместо создания нового класса исключений. –

+0

Спасибо CodeSavvyGeek, вы сохранили мне некоторую кодировку. :-) –

+0

Обновлен мой ответ на использование 'OperationCanceledException' ... –

0

Ваших вложенные типы могут принять делегат (или разоблачить событие), чтобы проверить для отмены состояния. Затем ваш менеджер передает делегату вложенные типы, которые проверяют свой собственный «shouldStop» логический. Таким образом, единственная зависимость от типа ManagerType от NestedType, которую вы уже имели в любом случае.

class NestedType 
{ 
    // note: the argument of Predicate<T> is not used, 
    // you could create a new delegate type that accepts no arguments 
    // and returns T 
    public Predicate<bool> ShouldStop = delegate() { return false; }; 
    public void DoWork() 
    { 
     while (!this.ShouldStop(false)) 
     { 
      // do work here 
     } 
    } 
} 

class ManagerType 
{ 
    private bool shouldStop = false; 
    private bool checkShouldStop(bool ignored) 
    { 
     return shouldStop; 
    } 
    public void ManageStuff() 
    { 
     NestedType nestedType = new NestedType(); 
     nestedType.ShouldStop = checkShouldStop; 
     nestedType.DoWork(); 
    } 
} 

Вы можете абстрагировать это поведение в интерфейсе, если хотите.

interface IStoppable 
{ 
    Predicate<bool> ShouldStop; 
} 

Кроме того, вместо того, чтобы просто проверять логическое значение, вы могли бы исключить механизм «остановки». В методе checkShouldStop менеджера, он может просто бросаться OperationCanceledException:

class NestedType 
{ 
    public MethodInvoker Stop = delegate() { }; 
    public void DoWork() 
    { 
     while (true) 
     { 
      Stop(); 
      // do work here 
     } 
    } 
} 

class ManagerType 
{ 
    private bool shouldStop = false; 
    private void checkShouldStop() 
    { 
     if (this.shouldStop) { throw new OperationCanceledException(); } 
    } 
    public void ManageStuff() 
    { 
     NestedType nestedType = new NestedType(); 
     nestedType.Stop = checkShouldStop; 
     nestedType.DoWork(); 
    } 
} 

Я использовал эту технику до и найти его очень эффективным.

0

Вы можете сгладить свой стек вызовов, повернув каждый вызов DoWork() в команду с использованием шаблона Command. На верхнем уровне вы поддерживаете очередь команд для выполнения (или стек, в зависимости от того, как ваши команды взаимодействуют друг с другом). «Вызов» функции переводится в очередь на включение новой команды в очередь. Затем между обработкой каждой команды вы можете проверить, отменять или нет. Как:

void DoWork() { 
    var commands = new Queue<ICommand>(); 

    commands.Enqueue(new MoreWorkCommand()); 
    while (!isStopping && !commands.IsEmpty) 
    { 
     commands.Deque().Perform(commands); 
    } 
} 

public class MoreWorkCommand : ICommand { 
    public void Perform(Queue<ICommand> commands) { 
     commands.Enqueue(new DoMoreWorkCommand()); 
    } 
} 

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