# Methode in eigenem Thread auslagern



## Dagobert (25. Mrz 2009)

Einen wunder schönen guten Tag,

Ich habe ein mehr oder weniger kleines Problem (ich weis nicht wie groß XD):
Ich habe ein Hauptfenster und eine Klasse map. In der Klasse map wird die Karte für das Spiel gespeichert, und die Operationen für die Karte zu verfügung gestellt.
Nun möchte ich aus dem Hauptfenster die Karte laden. Da dies aber sehr lange dauern kann, möchte ich dieses in ein extra Thread auslagern, damit die GUI nicht gespert wird und ich ein ProgressMonitor verwenden kann.
Jedoch bekomme ich das nicht so ganz hin, dass ich die Methode in einem extra Thread ausgeführt wird. Desweitern müsste dieser Thread auf eine Variable aus dem Hauptfenster zugreiffen können und die Karte als Rückgabewert zurückgeben. Wie bekomme ich dies hin?
Ich habe versucht die Klasse map mit Runnable zu erweitern, aber dies ist ja nicht ganz mein Ziel oder?! Da ja so die ganze Karte als eignes Thread läuft, kann ich doch späte Probleme bei den Animmationen bekommen, da ich ja die Frame- bzw. Delta-Zeiten aus den Hauptfenster beachten muss, oder?

Das Hauptfenster:

```
public class MapEditor extends JPanel implements Runnable {

	public void loadMap() {
		JFileChooser fc = new JFileChooser("resourcen/maps/");
		fc.setDialogTitle("Karte wählen");
		fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
		fc.setAcceptAllFileFilterUsed(false);
		fc.setFileFilter(new javax.swing.filechooser.FileFilter() {
			public boolean accept(File f) {
				return f.isDirectory()
						|| f.getName().toLowerCase().endsWith(".mapi");
			}

			public String getDescription() {
				return "map(*.mapi)";
			}
		});
		
		int returnvalue = fc.showOpenDialog(null);
		if (returnvalue == JFileChooser.APPROVE_OPTION) {
			File selecteFile = fc.getSelectedFile();
				System.out.println("Karte wird geladen");
				map = map.loadMap(selecteFile.getAbsolutePath());
		}	
	}
}
```

Die Karte:

```
public class Map{
	
	private String name;				// Name der Karte
	private String author;				// Autor der Karte
	private String description;			// Beschreibung der Karte selbst
	private long height;				// Aktuelle Höhe der Karte
	private long width;					// Aktuelle Breite der Karte
	
	private HashMap<Integer, Tile> availableTiles;	// Verfügbare Tiles werden gespeichert
	private Vector<Tile> tiles;				// Speicher für die Tiles der Karte
	private Vector<Objects> objects;		// Speicher für die Objekte der Karte
	
	//Hauptkonstruktor, der immer alle Atribute erwartet
	public Map(int height, int width, String name, String author, String description, Vector<Tile> tiles){
		
		setHeight(height);
		setWidth(width);
		setName(name);
		setAuthor(author);
		setDescription(description);
		copyTile(tiles);
		
		availableTiles = new HashMap<Integer, Tile>();
		tiles = new Vector<Tile>();
	}
	
	public Map(){
		this(10, 10 , "", "", "", new Vector<Tile>());
	}
	
	public Map(int height, int width){
		this(height, width, "", "", "", new Vector<Tile>());
	}
	
	public Map(int height, int width, Vector<Tile> tiles){
		this(height, width, "", "", "", tiles);
	}
				
	public Map loadMap(String openFile){
		System.out.println(openFile);
		FileReader fr = null;
		Map map = new Map();
		try {
			fr = new FileReader(openFile);
		} catch (FileNotFoundException e) {
			System.out.println("Karte konnten nicht geladen oder gefunden werden");
			e.printStackTrace();
			return null;
		}
		try {
			Document xml = new SAXBuilder().build(fr);
			Element root= xml.getRootElement();
			Element eigenschaften = root.getChild("eigenschaften");
			Element tilesInfo = root.getChild("tiles");
			map.setName(eigenschaften.getAttributeValue("name"));
			map.setAuthor(eigenschaften.getAttributeValue("autor"));
			map.setDescription(eigenschaften.getAttributeValue("beschreibung"));
			map.setWidth(eigenschaften.getAttribute("breite").getLongValue());
			map.setHeight(eigenschaften.getAttribute("hoehe").getLongValue());
		    File f = new File("./resourcen/tiles/");
		    
			String[] filenames = f.list(new FilenameFilter(){
				public boolean accept(File dir, String name){
					return name.endsWith(".ttr");
		    }});
						
			for(int i = 0; i < filenames.length; i++){
				Tile tile = new Tile();
				tile = tile.loadTile("./resourcen/tiles/" + filenames[i]);
				map.availableTiles.put(tile.getID(),tile);
			}
										
			for(int i = 1; i <= tilesInfo.getAttribute("anzahl").getLongValue();i++){
				Element currentTileInfo = tilesInfo.getChild("t"+i);
				Tile tile =  map.availableTiles.get(currentTileInfo.getAttribute("id").getIntValue());
				tile.setPosition(currentTileInfo.getAttribute("xpos").getIntValue(), currentTileInfo.getAttribute("ypos").getIntValue());
				addTile(tile);
			}
		} catch (JDOMException e) {
			e.printStackTrace();
			return null;
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		}
		return map;
	}
}
```

mfg. Dagobert


----------



## Spacerat (25. Mrz 2009)

Schau dir mal "java.util.concurrent,FutureTask" an (JDK/JRE1.6). Da kann man z.B. ein anonymes "Runnable" und ein Objekt der Klasse die man zurückgegeben haben will übergeben. Ich selber hab' zwar mit diesem Paket selber noch nicht gearbeitet, aber vllt. gehts damit ja besser, als eine komplette innere oder eingebettete Klasse selbst zu basteln.


----------



## Marco13 (25. Mrz 2009)

Wichtig wäre auch zu wissen, was dort wo gestartet wird. Ein "new Thread" habe ich beim Überfliegen jedenfalls nicht gesehen...


----------



## Dagobert (25. Mrz 2009)

Da wird noch nix gestartet, weil ich auf dem Schlauch stehe und das auch nur ausschnitte sind, da bestimmt keiner lust hat den ganzen code zu durch zu gucken oder? (sind immerhin schon >1000 Zeilen)
Also ich starte die Hauptkasse als Thread, aber wenn ich einfach so die Karte dann durch das gleiche Thread laden lasse schläft die GUI ein wenn ich ein JDialo/Progressmonitor aufbauen will, oder ich mache dabei auch was falsch. Es geht mir eigentlich nur darum eine Statusanzeige für das Laden einzubauen, wenn wer ne laternative zu meiner Threadidee hat bin ich gerne dafür offen 

mfg. Dagobert


----------



## Spacerat (25. Mrz 2009)

Marco13 hat gesagt.:
			
		

> Wichtig wäre auch zu wissen, was dort wo gestartet wird. Ein "new Thread" habe ich beim Überfliegen jedenfalls nicht gesehen...


Tja... so recht weis ich das auch nicht... Müsste da auch erst Experimentieren. z.B. mit solch einem Ansatz:
	
	
	
	





```
new Thread(new RunnableFuture<ReturnObject>()
{
  public boolean cancel(boolean mayInterruptIfRunning)
  {
    // ...
  }

  public boolean isCanceled()
  {
    // ...
  }

 public ReturnObject get(long value, TimeUnit tu)
  {
    // ...
  }

  public ReturnObject get()
  {
    // ...
  }

  boolean isDone()
  {
    // ...
  }

  void run()
  {
    // ...
  }
}, "EXPERIMENTAL").start();
```
Versuch was draus zu machen... good Luck...
@Edit: Huch... Hab' mich angesprochen gefühlt, weil ich bei meinem Vorschlag so etwas auch nicht gesehen habe...


----------



## Dagobert (25. Mrz 2009)

Danke für die Anregung.
Ich werde mal gucken was ich raus mache kann. Und fallls nicht weiter komme melde ich mich wieder und rücke mit mehr Geheimnissen raus.
Aber noch eine Frage kann ich einem Thread Parameter mit auf dem weg geben oder wie komme ich an meinen Filename ran? Und wöfür steht das "EXPERIMENTAL" als 2ter Paramter beim Threadaufruf?

mfg. Dagobert


----------



## Spacerat (26. Mrz 2009)

Das "EXPERIMENTAL" soll der Thread-Name sein. Hier kennzeichnet er, das der Code nicht gestestet wurde. Kenn geändert aber auch weggelassen werden.


----------



## Dagobert (26. Mrz 2009)

Ich hab jetzt mal das so aufgebaut:

```
setMap(new Thread(new RunnableFuture<Map>(){
				
				private Map map = null;
								
				public void run(){
					
					map = new Map();
					
					JFileChooser fc = new JFileChooser("resourcen/maps/");
					fc.setDialogTitle("Karte wählen");
					fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
					fc.setAcceptAllFileFilterUsed(false);
					fc.setFileFilter(new javax.swing.filechooser.FileFilter() {
						public boolean accept(File f) {
							return f.isDirectory()
									|| f.getName().toLowerCase().endsWith(".mapi");
						}

						public String getDescription() {
							return "map(*.mapi)";
						}
					});
					
					int returnvalue = fc.showOpenDialog(null);
					if (returnvalue == JFileChooser.APPROVE_OPTION) {
						File selecteFile = fc.getSelectedFile();
						map = map.loadMap(selecteFile.getAbsolutePath());
					}
				}

				@Override
				public Map get() throws InterruptedException,
						ExecutionException {
					System.out.println("Get zurück");
					return map;
				}

				@Override
				public Map get(long arg0, TimeUnit arg1)
						throws InterruptedException, ExecutionException,
						TimeoutException {
					// TODO Auto-generated method stub
					return null;
				}

				@Override
				public boolean isCancelled() {
					// TODO Auto-generated method stub
					return false;
				}

				@Override
				public boolean isDone() {
					// TODO Auto-generated method stub
					return false;
				}

				@Override
				public boolean cancel(boolean mayInterruptIfRunning) {
					// TODO Auto-generated method stub
					return false;
				}
			},"MAPLOAD").start());
	}

	public void setMap(Map map) {
		this.map = map;
	}

	public Map getMap() {
		return this.map;
	}
```

Wie bekomme ich denn jetzt das Object zurückgegeben, er macht das ja nicht von alleine wie ich feststellen musste^^

mfg. Dagobert


----------



## yujin (26. Mrz 2009)

Hallo,

probier mal folgendes:


```
public class MapEditor extends JPanel implements Runnable {

    public void loadMap() {
        JFileChooser fc = new JFileChooser("resourcen/maps/");
        fc.setDialogTitle("Karte wählen");
        fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
        fc.setAcceptAllFileFilterUsed(false);
        fc.setFileFilter(new javax.swing.filechooser.FileFilter() {
            public boolean accept(File f) {
                return f.isDirectory()
                        || f.getName().toLowerCase().endsWith(".mapi");
            }

            public String getDescription() {
                return "map(*.mapi)";
            }
        });
        
        int returnvalue = fc.showOpenDialog(null);
        if (returnvalue == JFileChooser.APPROVE_OPTION) {
    
// SwingWorker anlegen
            SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {
                @Override
                protected Void doInBackground() {
                    File selecteFile = fc.getSelectedFile();
                    System.out.println("Karte wird geladen");
                    map = map.loadMap(selecteFile.getAbsolutePath());        
                    return null;
                }
                @Override
                public void done() {
                    
                }
            };
// SwingWorker ausführen, Aufgabe läuft im Hintergrund
            worker.execute();            
        }    
    }
}
```
Mit dem SwingWorker lagerst du deine aufgabe in einem neuen Thread aus und die GUI wird dadurch nicht blockiert.


----------



## Spacerat (26. Mrz 2009)

OK... Back to the Roots... Dann eben Ohne "concurrent"...
Hab' hier mal, wenn auch recht dürftig, einen "MapManager" (Singleton!!!) entwickelt. Dort lassen sich "LoadEventListener" registrieren, die den Status (LoadLevel) abfragen und oder auf die Fertig geladene "Map" warten können.[highlight=java]import java.io.File;
import java.util.ArrayList;
import java.util.EventListener;
import java.util.EventObject;
import java.util.Iterator;
import java.util.TreeMap;

public final class MapManager
{
  private static volatile MapManager instance;

  private java.util.Map<File, Map> maps = new TreeMap<File, Map>();
  private final ArrayList<LoadEventListener> listener =
    new ArrayList<LoadEventListener>();

  private MapManager()
  {
  }

  public synchronized static MapManager getInstance()
  {
    if(instance == null) {
      instance = new MapManager();
    }
    return instance;
  }

  private void loadMapImpl(final File f)
  {
    final Map m = new Map();
    maps.put(f, m);
    new Thread(new Runnable()
    {
      public void run()
      {
        m.load(f);
        Iterator<LoadEventListener> i = listener.iterator();
        while(i.hasNext()) {
          i.next().loadProcessFinished(new LoadEvent(m));
        }
      }
    },"MAPLOAD").start();
  }

  /**
   * FileChooser muss an dieser Stelle bereits
   * aufgerufen worden sein
   * @param f
   * @return loaded Map
   */
  public synchronized Map loadMap(File f)
  {
    if(!maps.containsKey(f)) {
      loadMapImpl(f);
      return null;
    }
    if(!maps.get(f).isReady()) return null;
    return maps.get(f);
  } 

  public void addLoadEventListener(LoadEventListener lel)
  {
    if(!listener.contains(lel)) listener.add(lel);
  }

  public void removeLoadEventListener(LoadEventListener lel)
  {
    listener.remove(lel);
  }

  public interface LoadEventListener
  extends EventListener
  {
    void loadProcessFinished(LoadEvent e);
    void loadProcessUpdated(LoadEvent e);
  }

  public class LoadEvent
  extends EventObject
  {
    private static final long serialVersionUID = 1L; // supressWarning

    public LoadEvent(Object source)
    {
      super(source);
    }

    public Map getMap()
    {
      return (Map) getSource();
    }
  }

  public class Map
  {
    private float loadlevel;

    Map()
    {
      loadlevel = 0.0f;
    }

    void load(File f)
    {
      // Hier wird die Map geladen
      // der loadlevel aktualisiert und
      // prozesslistener informiert.
      Iterator<LoadEventListener> i = listener.iterator();
      while(i.hasNext()) {
        i.next().loadProcessUpdated(new LoadEvent(this));
      }
    }

    public float loadLevel()
    {
      return loadlevel;
    }
  }
}[/highlight]In der Laderoutine sollte noch irgendwo während geladen wird ein "Thread.sleep(10)" eingefügt werden, sonst lastets den Prozessor aus (100%).


----------

