Время, ограничивающее метод в C # - PullRequest
13 голосов
/ 14 августа 2010

У меня есть игровая платформа, в которой есть список ботов, которые реализуют 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();
        }
    }
}

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

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

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

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

Я пытаюсь понять, что я могу сделать с Задачей, медленно куда-то добраться.

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

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

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

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

Ответы [ 6 ]

10 голосов
/ 14 августа 2010

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

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

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

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

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

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

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

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

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

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

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

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

4 голосов
/ 14 августа 2010

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

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

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

редактировать

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

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

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

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

  4. Profit!

2 голосов
/ 14 августа 2010

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

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

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

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

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

Редактировать

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

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

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

2 голосов
/ 14 августа 2010

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

1 голос
/ 14 августа 2010

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

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

0 голосов
/ 14 августа 2010

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

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