Дождитесь завершения FutureTask в игровом цикле


Я хочу настроить FutureTask для дорогостоящей задачи поиска пути. Поэтому я создал этот класс Callable.

public class GetPath implements Callable<List<Coordinate>> {

    private Coordinate start;
    private Coordinate end;

    public GetPath(Coordinate start, Coordinate end) {
        this.start = start;
        this.end = end;
    }

    @Override
    public List<Coordinate> call() throws Exception {

        IndexedNodeImplementation location = TestMap.mapgraph.getNodeByCoord(start.x, start.y);
        IndexedNodeImplementation destination = TestMap.mapgraph.getNodeByCoord(end.x, end.y);
        GraphPathImplementation resultPath = new GraphPathImplementation();

        List<Coordinate> path = new ArrayList<Coordinate>();

        TestMap.pathFinder.searchNodePath(location, destination, new HeuristicImplementation(), resultPath);

        if (resultPath.getCount() > 0)
        {
            for (int i = 1; i < resultPath.getCount(); i++)
            {
                path.add(new Coordinate(resultPath.get(i).origin(), true));
            }
        }
        return path;
    }
}

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

executor = Executors.newFixedThreadPool(1);
task = new FutureTask(new GetPath(creature.getLocation(), coordinate));
//TestMap.executor.execute(task);

executor.execute(task);

try {
    path = (List<Coordinate>)task.get();
    } catch (InterruptedException e) {
        System.out.println("Interrupted Exception");
        e.printStackTrace();
    } catch (ExecutionException e) {
        System.out.println("Execution Exception"); // <---
        e.printStackTrace();
    }

    executor.shutdown();
    //Works but is basically the same as just looking up a path in the main thread.

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

Поскольку этот метод все еще удерживает программу до конца, я попытался посмотреть FutureTask в цикле Игры. После этого я заполняю список путей и продолжаю работу с кодом.
        if (task.isDone())
        {
            try {
                path = (List<Coordinate>)task.get();
            } catch (InterruptedException e) {
                System.out.println("Interrupted Exception");
                e.printStackTrace();
            } catch (ExecutionException e) {
                System.out.println("Execution Exception");
                e.printStackTrace();
            }
            executor.shutdown();
            //TestMap.executor.shutdown();
        }
        else
        {
            return false;
        }
        //Continue with code and work with path which should now contain elements

Но это дает NullPointerException я не могу проследить. Я проверил все переменные в своем собственном коде,и ничто не кажется пустым. Возможно, я неверно истолковываю задача.isDone. Или, возможно, я должен оставить эту задачу исполнителю для обработки и использования исполнитель должен определить, выполнена ли задача, но я не уверен, как это сделать.

java.util.concurrent.ExecutionException: java.lang.NullPointerException
    at java.util.concurrent.FutureTask.report(FutureTask.java:122)
    at java.util.concurrent.FutureTask.get(FutureTask.java:188)
    at com.buckriderstudio.buriedkingdoms.Creatures.Jobs.GoTo.perform(GoTo.java:55)
//...

Я просто ищу, чтобы запустить какой-нибудь дорогой код в другом потоке, не прерывая мой основной игровой цикл. Мне все равно, сколько времени это займет, пока он не найдет путь, поэтому я скорее помещаю его в отдельный поток, а затем пытаюсь реализовать гораздо лучший алгоритм поиска пути, такой как иерархический A*. В настоящее время я использую gdx.ai.pfa.* для поиска пути.

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

2 2

2 ответа:

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

У вас в основном есть условие гонки между строкой кода, которая обнуляет(или распоряжается) ресурсом, и кодом внутри Runnable, который должен получить к нему доступ. Другими словами, когда вы запускаете его синхронно, запускаемый всегда "добирается туда первым", так как остальная часть программы ждет его завершения. В вашем случае, когда он выполняется асинхронно, код удаления/обнуления выполняется до того, как запускаемый объект попадает в ресурс.

Вы обрезали трассировку стека, но в последней строке упоминается Метод perform, в GoTo.java

Вы делаете что-то с некоторыми ресурсами, извне Runnable, что означает, что во время выполнения runnable пытается получить доступ к чему-то, что является null.

Если вы посмотрите на Javadoc для Future, вы увидите, что get ожидает завершения задачи. Если вы вместо этого проверяете isDone в своем игровом цикле и вызываете get только один раз, который возвращает true, то вы будете работать асинхронно. Вот почему второй блок кода работает эффективно однопоточно.

В следующем примере это лучше и будет выполняться асинхронно, но вы вызываете это каждый раз, когда цикл, это означает, что вы закрыли ExecutorService, но задача все еще выполняется, так что вы затем снова вызовите isDone и get. Этот второй раз, вероятно, является тем, что порождает NullPointerException. Скорее всего, вам нужно что-то, чтобы остановить его многократный вызов, например, установка task в null и проверка task!=null && task.isDone().

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

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