Boost.Asio에서 비동기 작업은 핸들러를 통해 완료되며, 여러 비동기 작업을 동시에 수행할 경우 다중 핸들러의 관리가 필요하다. 특히, 이 과정에서 각 핸들러가 비동기 작업의 적절한 완료를 보장하고 충돌이나 자원 경합이 발생하지 않도록 관리하는 것이 중요하다.
다중 핸들러 구조의 필요성
여러 비동기 작업을 수행할 때, 각 작업에 대한 핸들러를 별도로 정의하고 관리해야 한다. 다중 핸들러 구조를 활용하면 여러 작업을 병렬로 처리하면서도 각 작업의 완료 여부를 추적할 수 있다. 이러한 구조는 특히 다음과 같은 경우에 유용하다:
- 여러 개의 소켓을 비동기로 관리할 때: 각 소켓에 대해 별도의 핸들러가 필요하다.
- 비동기 타이머와 네트워크 통신을 결합할 때: 각각의 작업에 대해 다른 핸들러가 호출된다.
- 작업이 완료될 때마다 특정 자원을 해제하거나 추가 작업을 시작할 때: 작업 순서를 보장하면서 여러 핸들러를 관리해야 한다.
Boost.Bind를 활용한 다중 핸들러 관리
Boost.Bind는 특정 핸들러를 작업과 연계할 수 있는 기능을 제공하며, 다중 핸들러 관리에 매우 유용하다. Boost.Bind는 함수 객체를 생성하여 매개변수를 고정하거나, 호출될 때 특정 데이터를 전달할 수 있다. 이를 통해 동일한 함수 핸들러에 여러 다른 매개변수를 전달하여 다중 핸들러 구조를 구현할 수 있다.
예제 코드: 여러 소켓에서의 비동기 읽기 작업 관리
boost::asio::io_service io_service;
boost::asio::ip::tcp::socket socket1(io_service);
boost::asio::ip::tcp::socket socket2(io_service);
// 첫 번째 소켓에 대한 비동기 읽기 작업
boost::asio::async_read(socket1, buffer1,
boost::bind(&handler_function, _1, _2, socket1));
// 두 번째 소켓에 대한 비동기 읽기 작업
boost::asio::async_read(socket2, buffer2,
boost::bind(&handler_function, _1, _2, socket2));
이 예제에서는 boost::bind
를 사용하여 두 개의 소켓에서의 비동기 읽기 작업에 대해 각각 다른 매개변수로 핸들러가 호출되도록 한다. 이처럼 Boost.Bind를 사용하면 동일한 핸들러 함수에 서로 다른 매개변수를 전달하여 다중 작업을 구조화할 수 있다.
다중 핸들러의 순차적 실행 관리
다중 비동기 작업이 있을 때 순차적으로 실행해야 할 경우가 있다. 순차적 실행을 보장하기 위해 Boost.Strand를 사용할 수 있으며, 이는 멀티스레드 환경에서 자원의 경합 없이 핸들러들이 순차적으로 호출되도록 한다. Boost.Strand는 비동기 핸들러의 순서를 보장하는 중요한 역할을 한다.
Boost.Strand의 사용 예시
boost::asio::io_service io_service;
boost::asio::strand strand(io_service);
// 비동기 작업 1
strand.post(boost::bind(&handler_function_1));
// 비동기 작업 2
strand.post(boost::bind(&handler_function_2));
이 구조는 멀티스레드 환경에서도 두 핸들러가 순차적으로 실행됨을 보장하며, 작업 간의 의존성이 있을 때 유용하다.
핸들러 간 데이터 전달
핸들러 간에 데이터를 전달하기 위해서는 핸들러가 호출될 때 특정 데이터를 캡처하거나 외부에서 접근 가능한 변수를 사용해야 한다. Boost.Bind는 이러한 데이터 전달을 간결하게 처리할 수 있는 도구를 제공한다. 예를 들어, 두 비동기 작업이 서로 연관되어 있을 경우 첫 번째 핸들러에서 생성된 데이터를 두 번째 핸들러로 전달할 수 있다.
수식으로 표현하자면, 첫 번째 작업의 결과를 두 번째 작업에 전달하는 것을 다음과 같이 정의할 수 있다.
여기서 \mathbf{R_1}은 첫 번째 작업의 결과, f는 데이터를 처리하는 함수, \mathbf{R_2}는 두 번째 작업의 입력으로 사용되는 데이터다.
void first_handler(const boost::system::error_code& ec, std::size_t bytes_transferred, boost::asio::strand& strand)
{
// 데이터를 처리하고 두 번째 핸들러로 전달
strand.post(boost::bind(&second_handler, processed_data));
}
위 예제는 첫 번째 핸들러에서 데이터를 처리한 후 strand
를 통해 두 번째 핸들러로 데이터를 전달하는 구조를 보여준다.
다중 핸들러의 비동기 작업 처리
비동기 핸들러는 서로 다른 작업을 동시에 처리할 수 있어야 한다. 다중 핸들러 관리에서는 비동기 작업이 완료되는 순서를 보장하거나, 작업 간 상호 의존성을 처리하는 것이 중요하다. Boost.Function과 Boost.Bind를 사용하면 이 과정을 더 간결하게 만들 수 있다.
핸들러 호출 흐름 관리
다중 핸들러를 관리할 때, 각 작업의 완료 후 다음 작업이 의존적인 경우가 많다. 이를 위해 비동기 작업 완료를 감지하고 후속 작업을 실행해야 한다. 예를 들어, 작업 \mathbf{T_1}, \mathbf{T_2}, \mathbf{T_3}이 순차적으로 실행되어야 할 때, 각 작업의 결과는 다음 작업의 입력이 된다.
여기서 \mathbf{R_1}, \mathbf{R_2}는 각각의 작업에서 생성된 결과물로, 이는 다음 작업의 시작 매개변수로 사용된다.
Boost.Bind를 사용하면 이 의존성을 코드에서 직접 관리할 수 있다. 각 작업이 완료될 때마다 후속 작업을 호출하는 방식으로 구현된다.
void handler_1(const boost::system::error_code& ec, std::size_t bytes_transferred)
{
// 첫 번째 작업 완료 후 두 번째 작업 호출
boost::asio::async_read(socket, buffer,
boost::bind(&handler_2, _1, _2));
}
void handler_2(const boost::system::error_code& ec, std::size_t bytes_transferred)
{
// 두 번째 작업 완료 후 세 번째 작업 호출
boost::asio::async_write(socket, buffer,
boost::bind(&handler_3, _1, _2));
}
멀티스레드 환경에서의 다중 핸들러 관리
다중 핸들러가 멀티스레드 환경에서 동작할 때는 자원 경합이나 데이터 무결성 문제가 발생할 수 있다. 이러한 문제를 방지하기 위해 Boost.Asio는 strand를 제공한다. strand는 특정 핸들러들이 동일한 스레드에서 순차적으로 실행되도록 보장한다.
멀티스레드 환경에서 strand를 사용하지 않고 여러 핸들러를 관리할 경우, 핸들러들이 동시에 호출될 수 있기 때문에 다음과 같은 문제가 발생할 수 있다:
- 데이터 경합: 여러 핸들러가 동시에 동일한 자원에 접근하면 예기치 않은 데이터 손상이 발생할 수 있다.
- 순서 보장 실패: 작업 순서가 중요한 경우, 여러 스레드에서 동시에 실행되는 핸들러들로 인해 작업 순서가 어긋날 수 있다.
따라서 멀티스레드 환경에서 다중 핸들러를 관리할 때는 strand를 사용하여 핸들러들의 순차 실행을 보장해야 한다.
Strand와 스레드 풀을 이용한 비동기 작업 관리
Boost.Asio의 strand
와 io_service
는 멀티스레드 환경에서 비동기 작업을 안전하게 처리하는데 핵심적인 역할을 한다. 이를 활용하여 스레드 풀을 구성하고 다중 핸들러가 안정적으로 동작하도록 할 수 있다.
boost::asio::io_service io_service;
boost::asio::strand strand(io_service);
// 스레드 풀 생성
boost::thread_group thread_pool;
for (int i = 0; i < 4; ++i)
{
thread_pool.create_thread(boost::bind(&boost::asio::io_service::run, &io_service));
}
// 첫 번째 비동기 작업
strand.post(boost::bind(&handler_1));
// 두 번째 비동기 작업
strand.post(boost::bind(&handler_2));
// 스레드 풀을 종료하고 자원 정리
thread_pool.join_all();
이 예제에서는 4개의 스레드로 구성된 스레드 풀이 있으며, 각 스레드는 strand에 의해 관리되는 비동기 작업을 수행한다. strand.post()
를 통해 각 핸들러를 순차적으로 실행시키며, 다중 핸들러가 동시에 실행되지 않도록 보장한다.
핸들러 간의 오류 처리
비동기 작업에서 오류 처리는 매우 중요한 부분이다. 각 작업이 완료되면 오류가 발생할 수 있으며, 이를 적절하게 처리해야 후속 작업에 영향을 미치지 않는다. Boost.Bind와 Boost.Function을 활용한 다중 핸들러 관리에서는 오류가 발생한 경우 오류 상태를 다른 핸들러로 전달하는 방식으로 처리할 수 있다.
수식으로 표현하자면, 오류 상태가 발생했을 때 후속 작업에 영향을 미치는 방식은 다음과 같이 정의된다:
여기서 \mathbf{E_i}는 i번째 작업에서 발생한 오류를 의미하며, 이는 i+1번째 작업에 전달된다.
void handler_1(const boost::system::error_code& ec, std::size_t bytes_transferred)
{
if (!ec)
{
// 오류가 없으면 다음 작업 수행
boost::asio::async_read(socket, buffer,
boost::bind(&handler_2, _1, _2));
}
else
{
// 오류 처리
std::cerr << "Error: " << ec.message() << std::endl;
}
}
이 코드는 비동기 작업에서 오류가 발생한 경우 이를 처리하고, 오류가 없는 경우에만 다음 작업을 호출한다. 이를 통해 오류가 발생한 작업 이후의 흐름을 제어할 수 있다.
다중 핸들러 관리의 구조화
다중 핸들러 구조화는 대규모 비동기 작업 시스템에서 각 작업을 분리하고 적절하게 관리하는 방법을 제공한다. 이 구조화는 핸들러들의 의존 관계를 명확하게 하고, 각 작업이 완료된 후의 흐름을 예측 가능하게 한다.
핸들러의 구조화를 위해 가장 많이 사용되는 방법 중 하나는 작업을 모듈화하고, 각 모듈이 독립적으로 동작하면서도 필요 시 다른 모듈과 통신할 수 있도록 하는 것이다. 이를 통해 핸들러 간의 결합도를 낮추고, 시스템의 확장성과 유지보수성을 높일 수 있다.
위의 다이어그램은 다중 비동기 작업이 어떻게 순차적으로 연결되고, 오류가 발생했을 때 어떻게 처리되는지를 보여준다.
핸들러에서의 상태 관리
다중 핸들러를 관리할 때, 비동기 작업의 진행 상태를 추적하는 것은 매우 중요하다. 비동기 작업이 여러 개 있을 때 각 핸들러가 독립적으로 실행되더라도, 전체 작업의 진행 상황이나 상태를 추적하는 시스템이 필요하다. 이를 위해 상태 변수 또는 플래그를 활용할 수 있으며, Boost.Bind와 Boost.Function을 사용하여 이러한 상태를 각 핸들러로 전달할 수 있다.
상태를 활용한 핸들러 관리
비동기 작업의 상태는 보통 진행 중인 작업의 완료 여부나 오류 상태를 나타낸다. 상태 변수를 관리하는 방식은 작업이 완료될 때마다 해당 상태를 갱신하고, 그 상태에 따라 후속 작업을 결정하는 것이다. 상태 관리를 통해 비동기 핸들러들이 서로 독립적으로 작동하면서도, 전체적인 작업 흐름을 제어할 수 있다.
struct AsyncState {
bool task1_completed;
bool task2_completed;
bool error_occurred;
AsyncState() : task1_completed(false), task2_completed(false), error_occurred(false) {}
};
void handler_1(const boost::system::error_code& ec, std::size_t bytes_transferred, AsyncState& state)
{
if (!ec)
{
state.task1_completed = true;
// 다음 작업 호출
boost::asio::async_write(socket, buffer,
boost::bind(&handler_2, _1, _2, boost::ref(state)));
}
else
{
state.error_occurred = true;
std::cerr << "Error in handler 1: " << ec.message() << std::endl;
}
}
void handler_2(const boost::system::error_code& ec, std::size_t bytes_transferred, AsyncState& state)
{
if (!ec)
{
state.task2_completed = true;
}
else
{
state.error_occurred = true;
std::cerr << "Error in handler 2: " << ec.message() << std::endl;
}
}
이 예제에서 AsyncState
구조체는 각 작업의 완료 여부와 오류 상태를 추적하는 데 사용된다. 각 핸들러는 상태 변수를 갱신하면서 후속 작업을 호출하며, 이를 통해 작업의 진행 상황을 중앙에서 관리할 수 있다. boost::ref
를 사용하여 참조 형태로 상태를 전달함으로써 각 핸들러가 동일한 상태 객체를 공유하도록 한다.
다중 핸들러에서의 자원 관리
비동기 작업에서 자원 관리 역시 중요한 문제다. 각 핸들러가 실행될 때, 메모리, 네트워크 자원, 파일 핸들 등 다양한 자원을 사용하게 된다. 다중 핸들러 관리에서 자원의 효율적인 사용과 해제는 시스템의 성능과 안정성에 큰 영향을 미친다.
자원 해제 및 핸들러 종료 관리
비동기 핸들러가 실행되고 나면, 더 이상 필요하지 않은 자원을 해제해야 한다. 예를 들어, 네트워크 소켓을 사용한 핸들러가 완료된 후에는 해당 소켓을 닫아야 하며, 파일을 읽거나 쓴 후에는 파일 핸들을 해제해야 한다.
이를 수식으로 표현하면, 자원의 사용 및 해제 과정은 다음과 같다:
여기서 \mathbf{R}은 자원, \mathbf{T_1}, \mathbf{T_2}는 작업을 의미하며, 각 작업 이후에 자원이 적절하게 해제되는 과정을 나타낸다.
자원 해제는 다음과 같이 구현할 수 있다:
void handler_1(const boost::system::error_code& ec, std::size_t bytes_transferred, boost::asio::ip::tcp::socket& socket)
{
if (!ec)
{
// 작업 완료 후 소켓 닫기
socket.close();
}
else
{
std::cerr << "Error in handler 1: " << ec.message() << std::endl;
}
}
이 코드에서는 작업이 완료된 후에 소켓 자원을 해제하는 방식으로 자원 관리를 수행한다. 비동기 작업에서는 자원 해제 타이밍이 매우 중요하며, 자원을 너무 일찍 해제하면 다음 작업에서 사용할 수 없게 되고, 너무 늦게 해제하면 불필요한 자원을 계속 점유하게 된다.
다중 핸들러 간의 동기화 문제
비동기 작업에서 여러 핸들러가 동일한 자원을 공유하는 경우, 동기화 문제가 발생할 수 있다. 이 문제를 해결하기 위해서는 핸들러 간의 자원 접근을 동기화하여 자원 경합을 방지해야 한다.
Boost.Asio에서의 동기화는 주로 strand를 사용하여 해결할 수 있다. strand는 핸들러가 순차적으로 실행되도록 보장하며, 이를 통해 동일한 자원을 안전하게 공유할 수 있다.
예제: 동일한 자원에 대한 동기화 처리
boost::asio::io_service io_service;
boost::asio::strand strand(io_service);
int shared_resource = 0;
void handler_1(const boost::system::error_code& ec, std::size_t bytes_transferred)
{
strand.post([&shared_resource]() {
shared_resource += 1;
std::cout << "Handler 1 updated resource: " << shared_resource << std::endl;
});
}
void handler_2(const boost::system::error_code& ec, std::size_t bytes_transferred)
{
strand.post([&shared_resource]() {
shared_resource += 2;
std::cout << "Handler 2 updated resource: " << shared_resource << std::endl;
});
}
이 예제에서는 strand를 사용하여 shared_resource
에 대한 접근을 동기화한다. 두 개의 핸들러는 각각 strand를 통해 자원에 접근하며, 동시에 실행되지 않고 순차적으로 실행된다. 이렇게 하면 멀티스레드 환경에서도 자원의 동기화 문제를 해결할 수 있다.
핸들러의 재사용과 모듈화
다중 핸들러 관리에서 핸들러를 모듈화하고 재사용 가능한 형태로 만드는 것은 시스템의 확장성을 높이는 중요한 방법이다. 동일한 핸들러가 여러 비동기 작업에서 반복적으로 사용될 수 있다면, 코드 중복을 줄이고 유지보수성을 높일 수 있다.
핸들러를 재사용하려면 핸들러가 특정 작업에 종속되지 않고, 매개변수나 상태에 따라 유연하게 동작할 수 있어야 한다. Boost.Bind와 Boost.Function을 사용하면 이러한 핸들러 모듈화를 쉽게 구현할 수 있다.
void reusable_handler(const boost::system::error_code& ec, std::size_t bytes_transferred, std::function<void()> callback)
{
if (!ec)
{
// 핸들러가 완료된 후 콜백 호출
callback();
}
else
{
std::cerr << "Error: " << ec.message() << std::endl;
}
}
// 콜백을 통해 다른 작업 수행
boost::asio::async_read(socket, buffer,
boost::bind(&reusable_handler, _1, _2, [](){
std::cout << "Callback executed after async read." << std::endl;
}));
이 코드에서 reusable_handler
는 매개변수로 받은 콜백을 통해 작업을 유연하게 처리할 수 있으며, 다른 작업에서도 동일한 핸들러를 재사용할 수 있다.
다중 핸들러에서의 에러 복구 전략
비동기 작업을 처리하는 동안 핸들러가 여러 개일 경우, 각 핸들러에서 발생하는 오류에 대한 복구 전략을 세우는 것이 중요하다. 다중 핸들러 구조에서는 오류가 발생할 수 있는 여러 지점이 존재하며, 이들 지점에서 적절히 대응하지 않으면 전체 시스템에 영향을 미칠 수 있다.
비동기 작업에서 오류는 네트워크 연결의 실패, 데이터 전송 중단, 시간 초과 등 여러 요인으로 발생할 수 있다. 오류 복구는 작업의 중단을 최소화하고 시스템이 오류에 강건하도록 만드는 데 중점을 둔다.
에러 복구의 기본 구조
오류 복구는 주로 오류 발생 시 핸들러의 재실행, 실패한 작업의 재시도, 혹은 후속 작업의 중단을 통해 이루어진다. 이를 수식으로 표현하면, 오류가 발생했을 때 작업 \mathbf{T_i}가 실패하고 이를 복구하는 과정은 다음과 같다:
여기서 \mathbf{R_i}는 오류 복구 작업, \mathbf{T_i'}는 오류가 복구된 후의 작업을 의미한다.
오류 복구는 비동기 작업의 성격에 따라 다양한 방식으로 처리될 수 있다. 예를 들어, 소켓 통신에서 연결이 끊어졌다면 다시 연결을 시도하는 것이 일반적이다.
오류 발생 시 작업 재시도
비동기 핸들러에서 오류가 발생하면 이를 재시도할 수 있는 전략을 세우는 것이 중요하다. 이를 위해 작업이 실패한 경우 일정 시간 후에 다시 작업을 실행하는 방식이 자주 사용된다.
void handler_1(const boost::system::error_code& ec, std::size_t bytes_transferred, boost::asio::ip::tcp::socket& socket)
{
if (!ec)
{
// 작업이 성공적으로 완료됨
std::cout << "Handler 1 completed successfully." << std::endl;
}
else
{
// 오류 발생, 재시도 로직
std::cerr << "Error in handler 1: " << ec.message() << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1)); // 1초 후 재시도
boost::asio::async_read(socket, buffer,
boost::bind(&handler_1, _1, _2, boost::ref(socket)));
}
}
위 코드에서는 오류가 발생했을 때, 1초 후에 다시 작업을 재시도하는 방식으로 복구를 시도한다. 이렇게 하면 네트워크 일시적 장애와 같은 문제를 처리할 수 있다.
복구 가능한 오류와 치명적 오류의 구분
오류의 성격에 따라 복구 가능한 오류와 그렇지 않은 치명적인 오류를 구분하는 것이 중요하다. 예를 들어, 네트워크 통신에서 일시적인 연결 끊김은 재시도를 통해 복구될 수 있지만, 소켓이 더 이상 유효하지 않거나 서버와의 연결 자체가 영구적으로 끊어졌을 경우에는 더 이상 작업을 진행할 수 없다.
이를 수식으로 표현하면, 복구 가능한 오류와 복구 불가능한 오류의 구분은 다음과 같다:
여기서 \mathbf{F_i}는 복구 불가능한 오류 상태를 나타낸다.
void handler_2(const boost::system::error_code& ec, std::size_t bytes_transferred, boost::asio::ip::tcp::socket& socket)
{
if (!ec)
{
// 작업이 성공적으로 완료됨
std::cout << "Handler 2 completed successfully." << std::endl;
}
else if (ec == boost::asio::error::connection_reset)
{
// 복구 가능한 오류 - 연결이 끊어졌으므로 다시 연결 시도
std::cerr << "Connection reset, retrying..." << std::endl;
reconnect(socket);
}
else
{
// 치명적 오류 - 작업 중단
std::cerr << "Fatal error in handler 2: " << ec.message() << std::endl;
}
}
위 코드에서 connection_reset
오류는 복구 가능한 오류로 간주되어 다시 연결을 시도하지만, 치명적인 오류는 더 이상 복구하지 않고 작업을 중단한다.
작업 완료 후의 핸들러 체인 관리
비동기 핸들러가 완료된 후에 또 다른 핸들러를 실행해야 하는 경우, 핸들러 체인을 구성하여 순차적으로 작업을 처리할 수 있다. 이러한 체인 관리는 각 작업이 완료될 때마다 다음 작업이 자동으로 실행되도록 구조화하며, 여러 작업이 의존 관계에 있을 때 유용하다.
핸들러 체인을 구성할 때는 각 작업의 결과가 다음 작업의 입력으로 사용되는 구조를 고려해야 한다. 이를 수식으로 표현하면, 핸들러 체인은 다음과 같다:
여기서 \mathbf{R_i}는 i번째 작업의 결과, \mathbf{T_{i+1}}는 다음 작업을 의미한다.
핸들러 체인 관리 예제
void handler_1(const boost::system::error_code& ec, std::size_t bytes_transferred, boost::asio::strand& strand)
{
if (!ec)
{
// 첫 번째 작업 완료 후 두 번째 작업 호출
boost::asio::async_read(socket, buffer,
strand.wrap(boost::bind(&handler_2, _1, _2, boost::ref(strand))));
}
else
{
std::cerr << "Error in handler 1: " << ec.message() << std::endl;
}
}
void handler_2(const boost::system::error_code& ec, std::size_t bytes_transferred, boost::asio::strand& strand)
{
if (!ec)
{
// 두 번째 작업 완료 후 세 번째 작업 호출
boost::asio::async_write(socket, buffer,
strand.wrap(boost::bind(&handler_3, _1, _2, boost::ref(strand))));
}
else
{
std::cerr << "Error in handler 2: " << ec.message() << std::endl;
}
}
이 예제는 핸들러 체인을 구성하여 작업이 순차적으로 실행되도록 관리하는 방식이다. Boost.Asio의 strand.wrap
을 사용하여 핸들러가 순서대로 실행되도록 보장한다.
핸들러 체인에서의 작업 취소
비동기 작업 중간에 특정 작업이 취소되거나 중단되어야 하는 경우, 이를 처리하는 구조를 갖추는 것도 중요하다. 작업 취소는 주로 사용자 요청에 의해 발생하거나, 작업의 특정 조건이 충족되지 않았을 때 이루어진다. 작업 취소는 작업 체인 전체를 중단시킬 수도 있고, 부분적으로 중단할 수도 있다.
작업 취소의 흐름을 수식으로 표현하면, 다음과 같이 정의할 수 있다:
여기서 \mathbf{C_i}는 취소 상태를 나타내며, 전체 작업을 중단하거나 특정 작업만 중단할 수 있다.
void cancel_handler(const boost::system::error_code& ec, std::size_t bytes_transferred, boost::asio::strand& strand, bool& cancel_flag)
{
if (cancel_flag)
{
std::cerr << "Operation cancelled." << std::endl;
return;
}
if (!ec)
{
// 작업 완료 후 다음 작업 호출
boost::asio::async_read(socket, buffer,
strand.wrap(boost::bind(&cancel_handler, _1, _2, boost::ref(strand), boost::ref(cancel_flag))));
}
else
{
std::cerr << "Error: " << ec.message() << std::endl;
}
}
이 코드에서는 cancel_flag
를 통해 작업 취소를 처리하며, 작업 중간에 플래그가 활성화되면 작업을 중단한다.