Я в настоящее время в процессе модернизации наших длительных методов быть отменяемыми. Я планирую использовать систему.Резьбонарезной.Задачи.CancellationToken для реализации этого.
Наши методы, как правило, выполняют несколько длительных этапов (отправка команды, а затем ждать в основном для оборудования), например
void Run()
{
Step1();
Step2();
Step3();
}
Мой первый (возможно глупая) мысль об отмене превратит это в
bool Run(CancellationToken cancellationToken)
{
Step1(cancellationToken);
if (cancellationToken.IsCancellationRequested)
return false;
Step2(cancellationToken);
if (cancellationToken.IsCancellationRequested)
return false;
Step3(cancellationToken);
if (cancellationToken.IsCancellationRequested)
return false;
return true;
}
что выглядит откровенно ужасно. Это и"образец" и будет продолжаться внутри одного действия, тоже (а они обязательно точнее уже длинноватые). Это сделало бы нить.Метод abort() выглядят довольно сексуально, хотя я знаю его не рекомендуется.
Там чище шаблон для достижения этого, что не скрывает от логики приложения под большим количеством шаблонного кода?
Редактировать
В качестве примера для природы действий "запустить" метод может прочитать
void Run()
{
GiantRobotor.MoveToBase();
Oven.ThrowBaguetteTowardsBase();
GiantRobotor.CatchBaguette();
// ...
}
Мы контролируем различных единиц оборудования, которые должны быть синхронизированы, чтобы работать вместе.
Если действия так или иначе зависим в отношении потока данных в рамках метода, но может'т быть выполнены в параллельный вопрос, следующий подход может быть лучше читаемым:
void Run()
{
// list of actions, defines the order of execution
var actions = new List<Action<CancellationToken>>() {
ct => Step1(ct),
ct => Step2(ct),
ct => Step3(ct)
};
// execute actions and check for cancellation token
foreach(var action in actions)
{
action(cancellationToken);
if (cancellationToken.IsCancellationRequested)
return false;
}
return true;
}
Если действия, Дон'т нужна токен отмены, потому что вы можете разделить их на крошечные блоки, вы даже можете написать поменьше определения списка:
var actions = new List<Action>() {
Step1, Step2, Step3
};
что насчет продолжения?
var t = Task.Factory.StartNew(() => Step1(cancellationToken), cancellationToken)
.ContinueWith(task => Step2(cancellationToken), cancellationToken, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current)
.ContinueWith(task => Step3(cancellationToken), cancellationToken, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current);
Я признаю, что это'т достаточно, но руководство-это делать то, что вы сделали:
if (cancellationToken.IsCancellationRequested) { /* Stop */ }
...или немного короче:
cancellationToken.ThrowIfCancellationRequested()
Вообще, если вы можете передать токен отмены в отдельных ее этапов, вы можете распространять проверки такие, что они не'т насытить код. Вы также можете выбрать, чтобы не проверить constantly отмены; если при выполнении операций являются идемпотентными и разве'т ресурсоемкий, вы не'т обязательно проверять отмену на каждом этапе. Самое важное время, чтобы проверить перед возвратом результата.
Если вы'вновь проходя знак, чтобы все ваши шаги, вы могли бы сделать что-то вроде этого:
public static CancellationToken VerifyNotCancelled(this CancellationToken t) {
t.ThrowIfCancellationRequested();
return t;
}
...
Step1(token.VerifyNotCancelled());
Step2(token.VerifyNotCancelled());
Step3(token.VerifyNotCancelled());
Когда мне пришлось делать нечто подобное, я создал делегат, чтобы сделать это:
bool Run(CancellationToken cancellationToken)
{
var DoIt = new Func<Action<CancellationToken>,bool>((f) =>
{
f(cancellationToken);
return cancellationToken.IsCancellationRequested;
});
if (!DoIt(Step1)) return false;
if (!DoIt(Step2)) return false;
if (!DoIt(Step3)) return false;
return true;
}
Или, если нет никакого кода между ступенями, можно написать:
return DoIt(Step1) && DoIt(Step2) && DoIt(Step3);
Использовать блокировки()синхронизировать
резьба.Вызвать abort()` с критическими секциями.
Как правило, при прерывании потока's Вы'd должны рассмотреть два типа кода:
Первый тип-это тип мы не'т волнует, если пользователь просил отменить. Может быть, мы рассчитывали на сто миллиардов, и он не'т волнует?
Если мы'd с помощью стражи, такие как `CancellationToken, мы вряд ли испытаем их в каждой итерации второстепенного кода бы мы?
for(long i = 0; i < bajillion; i++){
if(cancellationToken.IsCancellationRequested)
return false;
counter++;
}
Так некрасиво. Поэтому для таких случаев - нить.Метод abort ()
- это просто находка.
К сожалению, как некоторые говорят, Вы можете'т использовать нить.Метод abort()` из-за атомный код, который абсолютно должен работать! Код уже вычитали деньги из вашей учетной записи, теперь он должен завершить сделку и перевести деньги на счет цели. Никто не любит деньги исчезают.
К счастью, у нас есть взаимное исключение, чтобы помочь нам с такого рода вещи. И C# делает это красиво:
//unimportant long task code
lock(_lock)
{
//atomic task code
}
И в другом месте
lock(_lock) //same lock
{
_thatThread.Abort();
}
Количество замков всегда будет в <=
количество стражи, потому что вы'd быть, желая часовые на второстепенного кода (чтобы прервать быстрее). Это делает код немного красивее, чем Сентинел версия, но оно также делает лучше отменить, потому что он не'т придется ждать за неважных вещей.
Также следует отметить, что исключение ThreadAbortException может быть поставлен в любом месте, даже в "Наконец-то" блоков. Эта непредсказуемость делает нить.Метод abort()` так спорно.
С замками вы'd и избежать этого можно только заперев все попробовать-поймать-наконец-то
блок. Его очистку в "Наконец-то" - это важно, тогда весь блок может быть заблокирована. попробовать
блоков, как правило, сделаны, чтобы быть как можно более коротким, предпочтительно одной линии, так что мы не'т замок ненужного кода.
Это делает нить.Метод abort()`, как вы говорите, немного меньше зла. Возможно, вы не хотите назвать его в потоке пользовательского интерфейса, хотя, как вы'вновь закрывая его.
Если рефакторинг допустимо, вы могли бы преобразовать шагом методов, таких, что существует один способ Шаг(число int)`.
Затем можно перебрать от 1 до n и проверить, если маркер отмене которого просит только один раз.
bool Run(CancellationToken cancellationToken) {
for (int i = 1; i < 3 && !cancellationToken.IsCancellationRequested; i++)
Step(i, cancellationToken);
return !cancellationToken.IsCancellationRequested;
}
Или, что то же самое: (Какой вы предпочитаете)
bool Run(CancellationToken cancellationToken) {
for (int i = 1; i < 3; i++) {
Step(i, cancellationToken);
if (cancellationToken.IsCancellationRequested)
return false;
}
return true;
}
Возможно, вы захотите взглянуть на яблоко'pattern NSOperation с в качестве примера. Это's более сложная, чем просто один способ отмены, но это'ы довольно прочным.
Я поражен, что никто не предложил стандартный, встроенный, способ обработки этого:
bool Run(CancellationToken cancellationToken)
{
//cancellationToke.ThrowIfCancellationRequested();
try
{
Step1(cancellationToken);
Step2(cancellationToken);
Step3(cancellationToken);
}
catch(OperationCanceledException ex)
{
return false;
}
return true;
}
void Step1(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
...
}
Хотя обычно вы не'т хотите, чтобы полагаться на нажатия чеки на более глубоких уровнях, в этом случае меры уже принимает CancellationToken и в любом случае должен быть чек (как любой нетривиальный метод принимает CancellationToken).
Это также позволяет вам быть как гранулированный или не гранулированный проверок по мере необходимости, т. е. до длительных / интенсивные операции.