Ограничение времени для метода в C #

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

Затем игра вызывает методы в ботах (надеюсь, параллельно) для различных событий, таких как yourTurn и roundStart. Я хочу, чтобы бот потратил ограниченное количество времени на обработку этих событий, прежде чем его заставили прекратить работу на компьютере.

Примером того, что я пытаюсь, является: (где NewGame является делегатом)

Parallel.ForEach(Bots, delegate(IBot bot)
                {
                    NewGame del = bot.NewGame;
                    IAsyncResult r = del.BeginInvoke(Info, null, null);
                    WaitHandle h = r.AsyncWaitHandle;
                    h.WaitOne(RoundLimit);
                    if (!r.IsCompleted)
                    {
                        del.EndInvoke(r);
                    }
                }
            );

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

Было бы здорово, если бы был какой-то

try { 
 bot.NewGame(Info);
} catch (TimeOutException) {
 // Tell bot off.
} finally {
 // Compute things.
}

, но я не думаю, что возможно сделать такую ​​конструкцию.

Цель этого состоит в том, чтобы изящно работать с ИИ, которые имеют случайные бесконечные циклы или занимают много времени для вычисления.

Другой возможный способ решения этой проблемы - иметь что-то вроде этого (с большим количеством C # и меньшим количеством псевдокода)

Class ActionThread {
    pulbic Thread thread { get; set; }
    public Queue<Action> queue { get; set; }

    public void Run() {
        while (true) {
            queue.WaitOne();
            Act a = queue.dequeue();
            a();
        }
    }

Class foo {
    main() {
        ....
        foreach(Bot b in Bots) {
            ActionThread a = getActionThread(b.UniqueID);
            NewGame del = b.NewGame;
            a.queue.queue(del);
        }
        Thread.Sleep(1000);
        foreach (ActionThread a in Threads) {
            a.Suspend();
        }
    }
}

Не самый чистый способ, но это будет Работа. (Я буду беспокоиться о том, как передать параметры и получить возвращаемые значения позже).

Было бы замечательно, если бы был какой-то

try { 
 bot.NewGame(Info);
} catch (TimeOutException) {
 // Tell bot off.
} finally {
 // Compute things.
}

, но я не думаю, что возможно сделать такую ​​конструкцию.

Цель этого состоит в том, чтобы изящно обрабатывать ИИ, которые имеют случайные бесконечные циклы или занимают много времени. время для вычислений.

Другой возможный способ решения этой проблемы - иметь что-то вроде этого (с большим количеством C # и меньшим количеством псевдокодов).

Class ActionThread {
    pulbic Thread thread { get; set; }
    public Queue<Action> queue { get; set; }

    public void Run() {
        while (true) {
            queue.WaitOne();
            Act a = queue.dequeue();
            a();
        }
    }

Class foo {
    main() {
        ....
        foreach(Bot b in Bots) {
            ActionThread a = getActionThread(b.UniqueID);
            NewGame del = b.NewGame;
            a.queue.queue(del);
        }
        Thread.Sleep(1000);
        foreach (ActionThread a in Threads) {
            a.Suspend();
        }
    }
}

Не самый чистый способ, но он будет работать. (Я буду беспокоиться о том, как передать параметры и получить возвращаемые значения позже).

Было бы замечательно, если бы был какой-то

try { 
 bot.NewGame(Info);
} catch (TimeOutException) {
 // Tell bot off.
} finally {
 // Compute things.
}

, но я не думаю, что возможно сделать такую ​​конструкцию.

Цель этого состоит в том, чтобы изящно обрабатывать ИИ, которые имеют случайные бесконечные циклы или занимают много времени. время для вычислений.

Другой возможный способ решения этой проблемы - иметь что-то вроде этого (с большим количеством C # и меньшим количеством псевдокодов).

Class ActionThread {
    pulbic Thread thread { get; set; }
    public Queue<Action> queue { get; set; }

    public void Run() {
        while (true) {
            queue.WaitOne();
            Act a = queue.dequeue();
            a();
        }
    }

Class foo {
    main() {
        ....
        foreach(Bot b in Bots) {
            ActionThread a = getActionThread(b.UniqueID);
            NewGame del = b.NewGame;
            a.queue.queue(del);
        }
        Thread.Sleep(1000);
        foreach (ActionThread a in Threads) {
            a.Suspend();
        }
    }
}

Не самый чистый способ, но он будет работать. (Я буду беспокоиться о том, как передать параметры и получить возвращаемые значения позже).

[Further Edit]

I'm not too sure what an appdomain is, from the looks of it I could do so, but it cant see how it would help

I hope not to expect malicious code. Trying to kill the other bots threads is not a valid way to win the game. I just wanted to give every bot a second to compute then move on with the gameflow so mainly expecting slow or bugged code here.

I'm trying to see what I can do with Task, getting somewhere slowly.

I'll read into what CAS can do, thank you guys

[Подробнее Редактировать]

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

Я решил, что буду использовать полностью серверную клиентскую систему с сокетами. Так что клиент может делать все, что он хочет, и я просто проигнорирую, если он отказывается отвечать на сообщения сервера. Жаль, что до этого дошло.

13
задан Raynos 14 August 2010 в 08:32
поделиться

6 ответов

К сожалению, не существует 100% безопасного способа завершить поток чисто , как вам кажется.

Есть много способов сделать это, но все они имеют свои побочные и отрицательные стороны, которые вы, возможно, захотите принять во внимание.

Единственный чистый, безопасный и одобренный способ сделать это - заручиться сотрудничеством с обсуждаемой нитью и задать это вежливо . Однако это 100% гарантия только в том случае, если вы контролируете код. Поскольку ты не такой, этого не будет.

Вот в чем проблема.

  • Если вы выполните Thread.Abort , вы рискуете оставить домен приложения в небезопасном состоянии. Могут быть оставленные файлы открытыми, сетевые соединения или соединения с базой данных остаются открытыми, объекты ядра остаются в недопустимом состоянии и т. Д.
  • Даже если вы поместите поток в его собственный домен приложения и отключите домен приложения после прерывания потока, вы можете Не будьте на 100% уверены, что у вашего процесса не возникнет проблем в будущем из-за этого.

Давайте посмотрим, почему сотрудничество тоже не будет стопроцентным.

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

Однако это исключение можно было поймать и проглотить.

Или рассматриваемый код может перейти в бесконечный цикл, который вообще не вызывает код вашей библиотеки, что возвращает вас к исходной точке, как чисто убить поток без его сотрудничества.

Что я уже сказал, вы не можете.

Однако есть один способ, который может сработать.Вы можете запустить бота в его собственный процесс, а затем убить процесс, когда он истечет. Это даст вам больше шансов на успех, потому что, по крайней мере, операционная система позаботится обо всех ресурсах, которыми она управляет, когда процесс завершится. Конечно, вы можете заставить процесс оставить поврежденные файлы в системе, так что, опять же, он не на 100% чист.

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

10
ответ дан 1 December 2019 в 23:13
поделиться

Задача .NET 4.0 дает вам значительную гибкость в отношении отмены.

Запустите делегата в Задаче , в которую вы передали CancelationToken , например this .

Здесь есть более полный пример .

править

В свете отзывов, я считаю, что правильный ответ - следовать этому плану:

  1. Ограничьте ИИ с помощью CAS, чтобы он не мог получить доступ к примитивам потоковой передачи или возиться с файлами.

  2. Дайте ему обратный вызов сердцебиения, что он морально обязан вызывать изнутри длинных циклов. Этот обратный вызов вызовет исключение, если поток занял слишком много времени.

  3. Если истекло время ИИ, зарегистрируйте для него слабый ход и дайте ему короткое время для вызова этого обратного вызова. Если этого не произошло, просто положите нить в режим ожидания до следующего поворота. Если он никогда не выйдет из него, он будет продолжать регистрировать слабые движения, пока процесс не убьет его из-за того, что он является фоновым потоком.

  4. Прибыль!

4
ответ дан 1 December 2019 в 23:13
поделиться

Одним из вариантов, безусловно, является создание нового Обсудите самостоятельно, а не полагайтесь на BeginInvoke. Вы можете реализовать тайм-аут через Thread.Join и (бесцеремонно) убить поток, если необходимо, через Thread.Abort.

2
ответ дан 1 December 2019 в 23:13
поделиться

Как я уже упоминал в ответе @Lasse, вам действительно стоит подумать об использовании защиты доступа кода, чтобы ограничить то, что ботам разрешено делать в системе. Вы действительно можете ограничить то, что ботам разрешено выполнять, мудро (включая удаление их возможности доступа к API потоковой передачи). Возможно, стоит подумать о том, чтобы относиться к каждому боту как к вирусу, поскольку вы не контролируете, что он может сделать с вашей системой, и я предполагаю, что ваша игра будет работать как приложение с полным доверием.

Тем не менее, если вы ограничите действия каждого бота с помощью CAS, вы сможете завершить поток, не опасаясь повреждения вашей системы.Если бот застревает в бесконечном цикле, я подозреваю, что единственным выходом для вас будет завершение потока, поскольку он программно никогда не сможет достичь никакого кода, который проверял бы наличие сигнала Thread.Abort.

Эта статья MSDN может дать вам некоторые рекомендации о том, как более эффективно завершить сбойный поток с помощью функции TerminateThread.

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

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

Править

Просто чтобы добавить место, куда люди могут пойти, посмотрите Список флагов разрешений безопасности даст вам представление о том, с чего начать. Если вы удалите разрешение SecurityPermissionFlag.ControlThread (в качестве примера, давая только разрешение коду на выполнение и исключая ControlThread), вы удалите их

возможность использовать определенные расширенные операции над потоками.

Я не знаю, какой объем операций недоступен, но на выходных будет интересно выяснить это.

2
ответ дан 1 December 2019 в 23:13
поделиться

Другой вариант разработки может заключаться в том, чтобы позволить каждому боту быть отдельным процессом, а затем использовать механизм IPC для обмена данными. Обычно вы можете завершить процесс без каких-либо ужасных последствий.

0
ответ дан 1 December 2019 в 23:13
поделиться

Я думаю, что единственная проблема - это перевернутое условие. Вы должны вызывать EndInvoke только в том случае, если задача завершилась успешно.

Примерное предложение:

var goodBots = new List<IBot>();
var results = new IAsyncResult[Bots.Count];
var events = new WaitHandle[Bots.Count];
int i = 0;
foreach (IBot bot in Bots) {
    NewGame del = bot.NewGame;
    results[i] = del.BeginInvoke(Info, null, null);
    events[i++] = r.AsyncWaitHandle;
}
WaitAll(events, RoundLimit);
var goodBots = new List<IBot>();
for( i = 0; i < events.Count; i++ ) {
    if (results[i].IsCompleted) {
        goodBots.Add(Bots[i]);
        EndInvoke(results[i]);
        results[i].Dispose();
    }
    else {
        WriteLine("bot " + i.ToString() + " eliminated by timeout.");
    }
}
Bots = goodBots;

Еще лучше было бы явно развернуть поток для каждого бота, чтобы можно было приостановить работу ботов, которые плохо себя ведут (или снизить их приоритет), чтобы они не продолжали красть процессорное время у хорошие боты.

1
ответ дан 1 December 2019 в 23:13
поделиться
Другие вопросы по тегам:

Похожие вопросы: