비동기 처리란?
파일채널에서 다뤘던 read/write 메서드나 IO기반 입출력에서 다뤘던 읽기/쓰기 메서드들을 메서드가 호출되면 해당 메서드가 실행을 마칠때까지 코드가 블로킹된다. 블로킹된 동안에는 UI와 상호작용 하는 코드나 이벤트 처리를 못하게 된다. 이를 방지하기 위해 멀티 스레드 환경을 구축하여 별도의 스레드에서 입출력 작업을 진행하기도 한다. 하지만 이러한 방식은 스레드를 폭증하게 만들고, 불특정 다수의 대용량 파일들을 입출력할때는 비효율적이다.
이를 해결하기위해 코드를 실행한 후, 그 코드가 종료되지 않더라도 바로 다음 코드를 실행하는 방식의 처리가 있는데 이를 비동기 처리라고 한다. 자바에서는 NIO기반의 파일 비동기 채널(AsynchronousFileChannel)을 통해 비동기 처리를 구현한다.
비동기 처리 과정
파일 비동기 채널에서는 데이터를 읽고 쓰기 위해 read()와 write() 메서드를 제공한다. 소스코드에서 read()와 write() 메서드를 호출하면 스레드풀에 해당 작업을 요청하여, 해당 메서드를 바로 리턴한다. 그러면 실제 메서드의 실행은 스레드풀이 맡게 되고, 소스코드는 바로 다음 코드를 실행하게 된다. 그리고 작업스레드는 해당 작업을 완료하게 되면 콜벡 메서드를 호출한다.
(그림)
비동기 파일 채널 (AsynchronousFileChannel) 생성과 닫기
비동기 파일 채널은 파일 채널과 마찬가지로 open() 메서드를 통해 생성할 수 있다. 이때 매게 변수를 어떻게 지정하냐에 따라 메서드가 둘로 나뉘다.
AsynchrounousFileChannel fileChannel = AsynchronousFileChannel.open(
Path file,
OpenOption... options
);
위와 같이 파일의 경로 객체와 열기 옵션 값을 매개 변수로 지정하면 기본 스레드풀을 이용해 비동기 파일 채널을 관리한다. 이 경우 개발자는 스레드풀의 최대 스레드 수를 지정할 수 없다. 실제 스레드 풀의 스레드 개수를 지정하고 싶다면 아래와 같이 파일 채널을 생성해주어야 한다.
AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
Path file,
Set<?> options,
ExecutorService executorService,
FileAttribute<?>... attrs
);
다음과 같이 3번째 매개변수로 직접 정의 스레드풀을 넣어주면 해당 스레드풀을 통해 비동기 파일 채널을 관리한다.
비동기 파일 채널을 닫을 때는 파일 채널과 마찬가지로 close() 메서드를 이용한다.
asynchronousFileChannel.close();
파일 읽기와 쓰기
위에 설명한 비동기 처리 방식에 따르면 우리는 파일을 읽고 쓸때, 이에 따른 콜백 메서드까지도 고려해서 코드를 작성해야 한다. 자바에서는 read() / write() 메서드를 통해 파일 비동기 채널의 읽기와 쓰기를 수행하는데 이때 콜백메서드와 콜백 메서드로 보낼 첨부 객체를 인자로 넣어준다.
read(ByteBuffer dst, long position, A attachment, CompletionHandler<Integer, A> handler);
write(ByteBuffer src, long position, A attachment, CompletionHandler<Integer, A> handler);
먼저 데이터를 읽고 쓸 버퍼를 지정해주고(src, dst), 파일을 읽을 위치와 쓸 위치를 두번째 인자로 넣어준다(position). 그후 세번째 인자로 첨부객체, 그리고 마지막 인자에 콜백 구현 객체를 넣어준다.
콜백(Completion Handler) 함수는 자바의 경우 Completion Handler 인터페이스의 구현 객체로 만들어진다. 이때 이 구현 객체는 작업이 정상적으로 종료된 경우 불러올 메서드(completed)와 작업이 실패 했을때 불러올 메서드(failed)를 정의 해주어야 한다.
completed(Integer result, A attachment)
failed(Throwable exc, A attachment)
completed() 메서드는 작업이 정상적으로 종료됐을때 호출되는 콜백 함수로 첫번째 인자인 result는 read() / write() 메서드의 리턴 값 즉, 읽고 쓴 바이트의 수이고, 두번째 인자인 attachment는 read()/write() 메서드 호출 당시 인자로 지정한 첨부객체이다. 두 함수 모두 리턴 값은 없다(void).
적용 예시
public class AsynchronousFileChannelExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors()
);
for (int i=0; i<10; ++i) {
Path path = "C:/ORLY/USER/file_ex" + i + ".txt";
path.createDirectories(path.getParent());
AsynchronousFileChannel asynFileChannel = AsynchronousFileChannel.open(
path,
Enumset.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE),
executorService
);
Charset charset = Charset.defaultCharset();
ByteBuffer byteBuffer = charset.encode("hello world");
class Attachment {
Path path;
AsynchronousFileChannel asynFileChannel;
Attachment(Path path, AsynchronousFileChannel asynFileChannel) {
this.path = path;
this.asynFileChannel = asynFileChannel;
}
}
Attachment attachment = new Attachment(path, asynFileChannel);
CompletionHandler<Integer, Attachment> completionHandler = new CompletionHandler
{
@Override
public void completed(Integer result, Attachment attachment) {
System.out.println(attachment.path.getFileName + " : " + result + "bytes wriiten");
try { attachment.fileChannel.close(); } catch (IOExcpeiton e) {}
}
@Override
public void failed(Throwable exc, Attachment attachment) {
exc.printStackTrace();
try { attachment.fileChannel.close(); } catch (IOExcpeiton e) {}
}
};
asynFileChannel(byteBuffer, 0, attachment, completionHandler);
}
Thread.sleep(1000);
executorService.shutdown();
}
}
'Java > Network' 카테고리의 다른 글
8편 : TCP 소켓통신을 통한 멀티플레이 기능 구현 - 클라이언트 구현 (0) | 2022.01.23 |
---|---|
[Java] NIO 기반 네트워킹 : TCP 넌블로킹(non-blocking) 채널 (0) | 2022.01.18 |
[Java] NIO 기반 입출력 : 파일 채널 (0) | 2022.01.11 |
[Java] 네트워크(Networking) : TCP 통신 (0) | 2022.01.04 |