interface FileReaderClient {
void readCompleted(String filename, byte[] data);
void readFailed(String filename, IOException ex);
}
En implementation av gränssnitten kommer nedan. Notera för det
första att tråden som hanterar inläsningen skapas i "servern",
dvs FileReader-objektet. Detta är den naturliga implementationen
eftersom tråden som hanterar inläsningen då skapas av objektet
som hanterar inläsningen. Notera även att om "callback" inte önskas
kan det undvikas genom att "klienten" skickar null som FileReaderClient.
Här kommer implementationen:
class FileReaderApp implements FileReaderClient { // Fragments
protected FileReader reader = new AFileReader();
public void readCompleted(String filename, byte[]
data) {
// ... use data ...
}
public void readFailed(String filename, IOException
ex){
// ... deal with failure ...
}
public void actionRequiringFile() {
reader.read("AppFile", this);
}
public void actionNotRequiringFile() {
...
}
}
class AFileReader implements FileReader {
public void read(final String fn, final FileReaderClient
c) {
new Thread(new Runnable() {
public
void run() { doRead(fn, c); }
}).start();
}
protected void doRead(String fn, FileReaderClient
client) {
byte[] buffer = new byte[1024];
// just for illustration
try {
FileInputStream
s = new FileInputStream(fn);
s.read(buffer);
if
(client != null) client.readCompleted(fn, buffer);
}
catch (IOException ex) {
if
(client != null) {
client.readFailed(fn, ex);
}
}
}
}
Ytterligare en intressant detalj är att metoderna readCompleted()
och readFailed() i klientobjektet kommer att exekveras av servertråden,
dvs inte av den tråd som anropade actionRequiringFile() och
därmed startade inläsningen. Detta kan leda till problem om klienttråden
försöker använda filen innan den är färdiginläst.
Kan detta ske duger det inte att, som detta styckes rubrik anger, klienten
tar emot ett svar men struntar i att vänta på det. Problemet kan
lösas genom att klienten anropar wait() innan den inlästa
filen används och servern anropar notifyAll() i metoden readCompleted()
(och readFailed()). Används denna lösning försvinner
dock mycket av fördelarna som nämndes i början av detta stycke
med att använda en separat tråd för inläsningen. Om
någon form av signalering mellan trådarna ska användas kan
det dessutom vara klokare att använda någon av nedanstående
två metodiker eftersom de ger en renare design.
public void actionRequiringFile() {
Thread t = new Thread(new Runnable()
{
public
void run() {
reader.read("Appfile");
}
});
t.start();
//... Do anything that does
not require the file.
try {
t.join();
}
catch (InterruptedException
e) {
return;
}
byte[] buf = reader.getFile();
if (buf != null) {
//...
Use the file.
} else {
//...
The file could not be read. Error handling needed!!
}
}
public void actionNotRequiringFile() {
//...
}
}
class AFileReader implements FileReader {
byte[] buffer = null;
public byte[] getFile() {
return buffer;
}
public void read(final String fn) {
byte[] buffer = new byte[1024];
try {
FileInputStream
s = new FileInputStream(fn);
s.read(buffer);
this.buffer
= buffer;
}
catch (IOException ex) {}
}
}
Notera att sändaren måste skapa tråden som läser in
filen, eller åtminstone känna till den, eftersom den ska kunna
anropa join() på den. Denna lösning är knappast den
bästa på det här problemet eftersom den tenderar att bli
rörigare än att använda sig av callback. En lösning med
join() kommer bäst till sin rätt om sändaren faktiskt
inte har behov av ett svar utan det räcker med att veta att mottagaren
har exekverat meddelandet till slut.
public void actionRequiringFile() {
FileContent content = reader.read("Appfile");
//... Do anything that does
not require the file.
byte[] buf = content.getContent();
if (buf != null) {
//...
Use the file.
} else {
//...
Could not get file content. Error handling needed!!
}
}
public void actionNotRequiringFile() {
//...
}
}
class AFileReader implements FileReader {
private static class FutureContent implements FileContent
{
private byte[] content;
private boolean ready = false;
public synchronized byte[]
getContent() {
while
(!ready) {
try {
wait();
}
catch (InterruptedException e) {
return null;
}
}
return
content;
}
public synchronized void
setContent(byte[] b) {
content
= b;
ready
= true;
notifyAll();
}
} //End of inner class FutureContent.
public FileContent read(final String fn) {
final FutureContent fc = new
FutureContent();
new Thread(new Runnable() {
public
void run() {
byte[] buffer = new byte[1024];
try {
FileInputStream s = new FileInputStream(fn);
s.read(buffer);
fc.setContent(buffer);
}
catch (IOException ex) {
fc.setContent(null);
}
}
}).start();
return fc;
}
}
Vår future är alltså klassen FutureContent. Klienten
kan när som helst försöka hämta dess innehåll (dvs
arrayen content) utan att bry sig om ifall det redan finns där.
FurureContent kommer, när inläsningen är klar, att
returnera antingen innehållet eller ett felmeddelande. I ovanstående
enkla implementation är felmeddelandet bara att content är
satt till null. En mer avancerad implementation skulle även
kunna spara information om eventuella undantag som kastades då filen
lästes in och sedan kasta dem igen då klienten anropar getContent().
En ständigt återkommande fråga är var inläsningstråden ska skapas. I programmet ovan skapas (och startas) den av FileReader.read(). Ett alternativ vore att read() i stället returnerade sin Runnable. Denna skulle då kunna exekveras i en trådpool i stil med den vi tittade på i föregående avsnitt.
Låt oss till sist konstatera att i ovanstående program kan klienten göra vad som helst från det den får en FutureContent returnerad tills den anropar dess getContent(). Vi har allstå inte implementerat dubbel synkron sändning. Det går dock lätt att göra till exempel genom att låta FileReader.read() anropa FutureContent.getContent() och sedan returnera innehållet till klienten.