Share via


방법: 지연 후 완료되는 작업 만들기

이 예제에서는 , , concurrency::cancellation_token_source, concurrency::timerconcurrency::cancellation_tokenconcurrency::task_completion_eventconcurrency::call 클래스를 사용하여 concurrency::task지연 후 완료되는 작업을 만드는 방법을 보여 줍니다. 이 메서드를 사용하여 데이터를 폴링하는 루프를 빌드할 수 있습니다. 시간 제한, 미리 결정된 시간에 대한 사용자 입력의 지연 처리 등을 도입할 수도 있습니다.

예: complete_after 및 cancel_after_timeout 함수

다음 예제에서는 complete_aftercancel_after_timeout 함수를 보여 줍니다. complete_after 함수는 지정된 지연 후에 완료되는 task 개체를 만듭니다. 이 함수는 지정된 지연 후에 timer 개체와 call 개체를 사용하여 task_completion_event 개체를 설정합니다. task_completion_event 클래스를 사용하여 스레드 후에 또는 값을 사용할 수 있는 다른 작업 신호 후에 완료되는 작업을 정의할 수 있습니다. 이벤트가 설정되면 수신기 작업이 완료되고 작업의 연속 실행이 예약됩니다.

비동기 에이전트 라이브러리의 timer 일부인 클래스 및 call 클래스에 대한 자세한 내용은 비동기 메시지 블록을 참조 하세요.

함수는 cancel_after_timeout 지정된 시간 제한 전에 해당 작업이 완료되지 않는 경우 작업을 취소하기 위해 함수를 기반으로 합니다 complete_after . cancel_after_timeout 함수는 두 개의 작업을 만듭니다. 첫 번째 작업은 성공을 나타내며 제공된 작업이 완료된 후에 완료됩니다. 두 번째 작업은 오류를 나타내고 지정된 시간 제한 후에 완료됩니다. cancel_after_timeout 함수는 성공하거나 실패한 작업이 완료되면 실행되는 연속 작업을 생성합니다. 실패한 작업이 먼저 완료되면 연속에서 토큰 소스를 취소하여 전체 작업을 취소합니다.

// Creates a task that completes after the specified delay.
task<void> complete_after(unsigned int timeout)
{
    // A task completion event that is set when a timer fires.
    task_completion_event<void> tce;

    // Create a non-repeating timer.
    auto fire_once = new timer<int>(timeout, 0, nullptr, false);
    // Create a call object that sets the completion event after the timer fires.
    auto callback = new call<int>([tce](int)
    {
        tce.set();
    });

    // Connect the timer to the callback and start the timer.
    fire_once->link_target(callback);
    fire_once->start();

    // Create a task that completes after the completion event is set.
    task<void> event_set(tce);

    // Create a continuation task that cleans up resources and
    // and return that continuation task.
    return event_set.then([callback, fire_once]()
    {
        delete callback;
        delete fire_once;
    });
}

// Cancels the provided task after the specifed delay, if the task
// did not complete.
template<typename T>
task<T> cancel_after_timeout(task<T> t, cancellation_token_source cts, unsigned int timeout)
{
    // Create a task that returns true after the specified task completes.
    task<bool> success_task = t.then([](T)
    {
        return true;
    });
    // Create a task that returns false after the specified timeout.
    task<bool> failure_task = complete_after(timeout).then([]
    {
        return false;
    });

    // Create a continuation task that cancels the overall task 
    // if the timeout task finishes first.
    return (failure_task || success_task).then([t, cts](bool success)
    {
        if(!success)
        {
            // Set the cancellation token. The task that is passed as the
            // t parameter should respond to the cancellation and stop
            // as soon as it can.
            cts.cancel();
        }

        // Return the original task.
        return t;
    });
}

예: 소수 계산 수

다음 예제에서는 [0, 100000] 범위에서 소수의 수를 여러 번 계산합니다. 지정된 시간 제한에서 완료되지 않으면 작업이 실패합니다. count_primes 함수는 cancel_after_timeout 함수를 사용하는 방법을 보여 줍니다. 지정된 범위의 소수 수를 계산하고 제공된 시간에 작업이 완료되지 않으면 실패합니다. wmain 함수는 count_primes 함수를 여러 번 호출합니다. 호출할 때마다 제한 시간은 1/2로 줄어듭니다. 현재 시간 제한에서 작업이 완료되지 않으면 프로그램이 완료됩니다.

// Determines whether the input value is prime.
bool is_prime(int n)
{
    if (n < 2)
        return false;
    for (int i = 2; i < n; ++i)
    {
        if ((n % i) == 0)
            return false;
    }
    return true;
}

// Counts the number of primes in the range [0, max_value].
// The operation fails if it exceeds the specified timeout.
bool count_primes(unsigned int max_value, unsigned int timeout)
{
    cancellation_token_source cts;

    // Create a task that computes the count of prime numbers.
    // The task is canceled after the specified timeout.
    auto t = cancel_after_timeout(task<size_t>([max_value, timeout, cts]
    {
        combinable<size_t> counts;
        parallel_for<unsigned int>(0, max_value + 1, [&counts, cts](unsigned int n) 
        {
            // Respond if the overall task is cancelled by canceling 
            // the current task.
            if (cts.get_token().is_canceled())
            {
                cancel_current_task();
            }
            // NOTE: You can replace the calls to is_canceled
            // and cancel_current_task with a call to interruption_point.
            // interruption_point();

            // Increment the local counter if the value is prime.
            if (is_prime(n))
            {
                counts.local()++;
            }
        });
        // Return the sum of counts across all threads.
        return counts.combine(plus<size_t>());
    }, cts.get_token()), cts, timeout);

    // Print the result.
    try
    {
        auto primes = t.get();
        wcout << L"Found " << primes << L" prime numbers within " 
              << timeout << L" ms." << endl;
        return true;
    }
    catch (const task_canceled&)
    {
        wcout << L"The task timed out." << endl;
        return false;
    }
}

int wmain()
{
    // Compute the count of prime numbers in the range [0, 100000] 
    // until the operation fails.
    // Each time the test succeeds, the time limit is halved.

    unsigned int max = 100000;
    unsigned int timeout = 5000;
    
    bool success = true;
    do
    {
        success = count_primes(max, timeout);
        timeout /= 2;
    } while (success);
}
/* Sample output:
    Found 9592 prime numbers within 5000 ms.
    Found 9592 prime numbers within 2500 ms.
    Found 9592 prime numbers within 1250 ms.
    Found 9592 prime numbers within 625 ms.
    The task timed out.
*/

이 기술을 사용하여 지연 후 작업을 취소하면 전체 작업이 취소된 후에 시작되지 않은 작업이 시작되지 않습니다. 그러나 장기 실행 작업은 취소에 신속하게 대응하는 것이 중요합니다. 작업 취소에 대한 자세한 내용은 PPL의 취소를 참조하세요.

전체 코드 예제

이 예제의 전체 코드는 다음과 같습니다.

// task-delay.cpp
// compile with: /EHsc
#include <ppl.h>
#include <ppltasks.h>
#include <agents.h>
#include <iostream>

using namespace concurrency;
using namespace std;

// Creates a task that completes after the specified delay.
task<void> complete_after(unsigned int timeout)
{
    // A task completion event that is set when a timer fires.
    task_completion_event<void> tce;

    // Create a non-repeating timer.
    auto fire_once = new timer<int>(timeout, 0, nullptr, false);
    // Create a call object that sets the completion event after the timer fires.
    auto callback = new call<int>([tce](int)
    {
        tce.set();
    });

    // Connect the timer to the callback and start the timer.
    fire_once->link_target(callback);
    fire_once->start();

    // Create a task that completes after the completion event is set.
    task<void> event_set(tce);

    // Create a continuation task that cleans up resources and
    // and return that continuation task.
    return event_set.then([callback, fire_once]()
    {
        delete callback;
        delete fire_once;
    });
}

// Cancels the provided task after the specifed delay, if the task
// did not complete.
template<typename T>
task<T> cancel_after_timeout(task<T> t, cancellation_token_source cts, unsigned int timeout)
{
    // Create a task that returns true after the specified task completes.
    task<bool> success_task = t.then([](T)
    {
        return true;
    });
    // Create a task that returns false after the specified timeout.
    task<bool> failure_task = complete_after(timeout).then([]
    {
        return false;
    });

    // Create a continuation task that cancels the overall task 
    // if the timeout task finishes first.
    return (failure_task || success_task).then([t, cts](bool success)
    {
        if(!success)
        {
            // Set the cancellation token. The task that is passed as the
            // t parameter should respond to the cancellation and stop
            // as soon as it can.
            cts.cancel();
        }

        // Return the original task.
        return t;
    });
}

// Determines whether the input value is prime.
bool is_prime(int n)
{
    if (n < 2)
        return false;
    for (int i = 2; i < n; ++i)
    {
        if ((n % i) == 0)
            return false;
    }
    return true;
}

// Counts the number of primes in the range [0, max_value].
// The operation fails if it exceeds the specified timeout.
bool count_primes(unsigned int max_value, unsigned int timeout)
{
    cancellation_token_source cts;

    // Create a task that computes the count of prime numbers.
    // The task is canceled after the specified timeout.
    auto t = cancel_after_timeout(task<size_t>([max_value, timeout, cts]
    {
        combinable<size_t> counts;
        parallel_for<unsigned int>(0, max_value + 1, [&counts, cts](unsigned int n) 
        {
            // Respond if the overall task is cancelled by canceling 
            // the current task.
            if (cts.get_token().is_canceled())
            {
                cancel_current_task();
            }
            // NOTE: You can replace the calls to is_canceled
            // and cancel_current_task with a call to interruption_point.
            // interruption_point();

            // Increment the local counter if the value is prime.
            if (is_prime(n))
            {
                counts.local()++;
            }
        });
        // Return the sum of counts across all threads.
        return counts.combine(plus<size_t>());
    }, cts.get_token()), cts, timeout);

    // Print the result.
    try
    {
        auto primes = t.get();
        wcout << L"Found " << primes << L" prime numbers within " 
              << timeout << L" ms." << endl;
        return true;
    }
    catch (const task_canceled&)
    {
        wcout << L"The task timed out." << endl;
        return false;
    }
}

int wmain()
{
    // Compute the count of prime numbers in the range [0, 100000] 
    // until the operation fails.
    // Each time the test succeeds, the time limit is halved.

    unsigned int max = 100000;
    unsigned int timeout = 5000;
    
    bool success = true;
    do
    {
        success = count_primes(max, timeout);
        timeout /= 2;
    } while (success);
}
/* Sample output:
    Found 9592 prime numbers within 5000 ms.
    Found 9592 prime numbers within 2500 ms.
    Found 9592 prime numbers within 1250 ms.
    Found 9592 prime numbers within 625 ms.
    The task timed out.
*/

코드 컴파일

코드를 컴파일하려면 코드를 복사한 다음 Visual Studio 프로젝트에 붙여넣거나 이름이 지정된 task-delay.cpp 파일에 붙여넣은 다음 Visual Studio 명령 프롬프트 창에서 다음 명령을 실행합니다.

cl.exe /EHsc task-delay.cpp

참고 항목

작업 병렬 처리
task 클래스(동시성 런타임)
cancellation_token_source 클래스
cancellation_token 클래스
task_completion_event 클래스
timer 클래스
call 클래스
비동기 메시지 블록
PPL에서의 취소