Ограничение времени методом в С#

У меня есть Game Framework, где есть список ботов, которые реализуют 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.
}

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

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

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

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

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

[Дальнейшее редактирование]

Я не слишком уверен в том, что такое appdomain, из его взглядов я могу это сделать, но он не может понять, как это поможет

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

Я пытаюсь посмотреть, что я могу сделать с Task, медленно и медленно.

Я прочитаю, что CAS может сделать, спасибо вам, ребята

[Больше править]

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

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

Ответы

Ответ 1

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

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

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

Здесь проблема.

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

Посмотрите, почему сотрудничество не будет равно 100%.

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

Однако это исключение можно поймать и усвоить.

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

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

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

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

Ответ 2

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

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

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

изменить

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

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

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

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

  • Profit!

Ответ 3

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

Ответ 4

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

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

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

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

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

Edit

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

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

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

Ответ 5

Я думаю, что единственная проблема - инвертированное условие. Вы должны называть 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;

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

Ответ 6

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