Eşzamansız Programlama – Bölüm 2: Boost.Asio ile C++'daki Eşyordamlar

Boost.Asio çerçevesiyle C++ geliştiricileri, modern C++'da hala yeri olan kanıtlanmış araçlar koleksiyonuna erişebilir. Bağlamlar, uygulayıcılar ve tamamlama belirteçleri ile eşzamansız programları çeşitli ilkelere göre temiz ve verimli bir şekilde geliştirmenize olanak tanır. geri aramalarda vadeli işlemler, spawn VE yield_context aynı zamanda eşyordamlara benzer stillere de izin verirler. Bu, Boost.Asio'nun temellerinin sunulduğu önceki makalede zaten gösterilmiştir.

Duyurudan sonra devamını okuyun

Martin Meeser bağımsız bir bilgisayar bilimcisidir (üniversite) ve yazılım geliştirme alanında hizmetler sunmaktadır: bireysel yazılım geliştirme, süreç danışmanlığı ve eğitim. Otomotiv, finans, uzay yolculuğu, radyo astronomi ve tıbbi teknoloji sektörleri de dahil olmak üzere çok sayıda projede müşterilerine destek vermiştir.

C++20'den itibaren derleyici tabanlı yığınsız eşyordamlar artık mevcuttur. Bununla birlikte Boost.Asio tüm gücünü gösterir. Geliştiriciler, anahtar sözcüklerden herhangi birini kullanarak bir eşyordamdaki herhangi bir işlevi veya yöntemi değiştirebilirler. co_yield, co_await mineral co_return sokmak. Derleyici bu işlevi bir durum makinesine (ortak yordam çerçevesi) dönüştürür ve bu makinenin yürütülmesi co_await VEYA co_yield elipsler işaretlendi ve sonra bir tanesinde std::coroutine_handle devam ediyor. Varsayılan olarak, durumu saklayan eşyordam çerçevesi yığında değil yığında bulunur.

Boost.Asio'da her eşyordamın bir tür örneğine sahip olması gerekir boost::asio::awaitable<T> geri dönmek. awaitable dönüş türünü kapsüller: awaitable<void> dönüş değeri olmayan bir fonksiyon için, awaitable<int> içinde, awaitable<std::string> dize türleri vb. için (Liste 1).


boost::asio::awaitable<void> async_void_sample()
{
    co_return;
}

boost::asio::awaitable<int> async_int_sample()
{
    co_return 42;
}

boost::asio::awaitable<std::string> async_string_sample()
{
    co_return "Hello async";

Liste 1: Geri dönüş türlerini bekleyebilirlikle kapsülleme

Fonksiyon, normal bir program kısmı ile bir ortak rutin kısmı arasında geçiş yapmak için kullanılır. boost::asio::co_spawn. Üç parametre bekliyorsunuz:

  1. icracı (veya execution_context dışarı convenience) üzerinde eşyordamların aralıklı veya paralel yürütülmesinin gerçekleştiği yer
  2. bir örneği awaitable<T>
  3. bir CompletionToken türü detachedişlev nesnesi veya use_future

Duyurudan sonra devamını okuyun

İle detached ırk awaitable sonucu işleyemeden süreç şubenizde. Bu genellikle ana yöntemde başlangıçta bir eşyordamı çağırmak için kullanılır (Liste 2).


int main()
{
    boost::asio::io_context io_context;

    // co_spawn mit CompletionToken Function-Object
    boost::asio::co_spawn(io_context, async_int_sample(), [](std::exception_ptr, 
        int result)
    {
        std::cout << "async_int_sample() = " << result << std::endl;
    });

    // co_spawn mit CompletionToken use_future
    std::future future = boost::asio::co_spawn(io_context, 
        async_string_sample(), boost::asio::use_future);

    // co_spawn mit CompletionToken detached
    boost::asio::co_spawn(io_context, async_caller(), boost::asio::detached);

    io_context.run();

    std::string s = future.get();
    std::cout << "async_string_sample() = " << s << std::endl;

Liste 2: Bir eşyordamı çağırmak için waitable'ı kullanan Co_spawn örnekleri.

Liste 3 kullanımını gösterir co_await. aradığınızda awaitable ile co_await şunlar olur:

  1. Bu noktada uygulayıcı eşyordamın yürütülmesini kesebilir. Kesintinin gerçekten gerçekleşip gerçekleşmediği bilinmiyor ve konuyla ilgisiz.
  2. Yürütücü diğer eylemleri gerçekleştirebilir: tek iş parçacıklı bir bağlamda eşzamanlı olarak veya çok iş parçacıklı bir bağlamda paralel olarak.
  3. Geliştiricinin bilmediği bir zamanda, uygulayıcı şunları getirir: awaitable konularının birinde execution_context infaz için – tam olarak neye dayanarak bilinmiyor ve alakasız.

Bir kez sonucu awaitable mevcut olduğunda, uygulayıcı eşyordama kaldığı yerden devam eder.


boost::asio::awaitable<int> async_callee(int i)
{
    std::cout << "hello from awaitable, i=" << i << std::endl;
    co_return i + 1;
}

boost::asio::awaitable<void> async_caller()
{
    int i = co_await async_callee(1);
    // erzeugt ein awaitable, es wird aber nicht ausgeführt
    boost::asio::awaitable<int> aw = async_callee(2);
    // awaitable kann nicht kopiert werden, move erforderlich
    // co_await std::move(aw);
    std::cout << "i=" << i << std::endl;

    co_return;
}

// Ausgabe:
hello from awaitable, i=1
i=2

Liste 3: co_await'i boost::asio::awaitable ile kullanma örneği.

Bireysel fonksiyon açısından süreç senkronizedir, yani fonksiyon sonuca ulaşana kadar durur. awaitable mevcut. Ancak geliştiricilerin işlevlerini çağırdığı iş parçacığı engellenmez. Konu diğer işbirlikçi eylemler için mevcuttu.

Geliştiriciler şunları yapabilir: awaitable yalnızca bir kez kullanın. Eğer bir awaitable üret ama onunla değil co_await çağrıldığında program işlevi yürütmez.

Liste 3 aynı zamanda nasıl yapılacağını da gösterir co_return tarafından döndürülen değer co_await-ifade awaitable<T>nesne çıkarılır ve çağıran işlevdeki bir değişkene atanır. Bu çok kullanışlı bir mekanizmadır, sonuçta hangi ipliğin olduğu bilinmemektedir. async_callee idama gelir.

İşlenmeyen istisnalar bir eşyordam içinde meydana gelir, çağıran eşyordamı kabul eder co_await aksine. Eşyordamlar, istisnaların meydana geldiği belirli iş parçacığına bakılmaksızın, senkronize işlevsellik ile aynı şekilde istisnaları yakalar. Liste 4'te kısa bir örnek gösterilmektedir.


#include <boost/asio.hpp>
#include <iostream>

boost::asio::awaitable<void> async_ex_sample()
{
    throw std::runtime_error{ "some exception" };
    co_return;
}

int main()
{
    boost::asio::thread_pool pool(4);
    boost::asio::co_spawn(pool, []()->boost::asio::awaitable<void>
    {
        try
        {
            co_await async_ex_sample();
        }
        catch (const std::exception& ex)
        {
            std::cout << ex.what() << std::endl;
        }
        co_return;
    }, boost::asio::detached);

    pool.join();
}

Liste 4: Çapraz İş Parçacığı İstisna İşleme.

Liste 5, geliştiricilerin Boost.Asio kütüphanesinin eşzamansız işlevlerini nasıl kullanabileceğini gösterir co_await kullanın: Bunu yapmak için CompletionToken'ı iletin use_awaitable yöntem parametresi olarak async_wait des timer – Nesne, dolayısıyla işlevin döndürdüğü tür artık awaitable.


boost::asio::awaitable<void> async_sample(
    boost::asio::steady_timer timer, 
    boost::asio::ip::tcp::socket socket)
{
    co_await timer.async_wait(boost::asio::use_awaitable);
    char buf[4096];
    std::size_t n = co_await socket.async_read_some(boost::asio::buffer(buf), 
         boost::asio::use_awaitable);
}

Liste 5: Boost.Asio işlevlerini co_await ve use_awaitablecompleteToken ile kullanma örneği.

Eşyordamların bir diğer avantajı da döngüleri olağan şekilde ortadan kaldırmalarıdır. for-O while-Stil aynı zamanda eşzamansız süreçler için de formüle edilebilir. Eşyordamlar olmadan yinelemeleri geri aramalara bölmeniz veya daha karmaşık durum makineleri yazmanız gerekir.

Liste 6'daki kod, sonsuz bir döngüdeki bir soketi okur ve alınan verileri doğrudan yeniden yazar: basit bir yankı sunucusu. Kod normal bir döngü gibi görünse de herhangi bir iş parçacığını engellemez. Herkes co_await-expression kontrolü uygulayıcıya döndürür. Veri mevcut olduğunda veya yazma işlemi tamamlandığında döngü kaldığı yerden devam eder – okuma ve yazma işlemlerinin hangi iş parçacığı üzerinde çalıştığı bilinmemektedir – bu, geliştiricinin programda kullandığı yürütücü tarafından belirlenir. co_spawn dedi.


boost::asio::awaitable<void> async_sample(boost::asio::ip::tcp::socket socket)
{
    char buf[4096];
    for (;;)
    {
        std::size_t n = co_await socket.async_read_some(
            boost::asio::buffer(buf),
            boost::asio::use_awaitable);

        co_await boost::asio::async_write(
            socket,
            boost::asio::buffer(buf, n),
            boost::asio::use_awaitable);
    }
}

Liste 6: Yineleme için eşzamansız örnek.

Önceki örneklerde gösterildiği gibi eşyordamlarla hata işlemeye yönelik birkaç seçenek vardır:

  1. hata işleme yok
  2. yakalamaya çalışmakla
  3. yerel hata değişkeni boost::asio::redirect_error tamamlama belirteci olarak
  4. CompletionToken ile bir tanımlama grubundan döndürülen değerin parçası olarak boost::asio::as_tuple.

Aşağıdaki Liste 7'de ilgili örnekler gösterilmektedir:


boost::asio::awaitable<void> errors_sample(boost::asio::ip::tcp::socket socket)
{
    boost::asio::any_io_executor executor = co_await    
        boost::asio::this_coro::executor;
    std::array<char, 4> buffer;

    // 1. ohne Fehler-Behandlung
    std::size_t n = co_await socket.async_read_some(boost::asio::buffer(buffer), 
        boost::asio::use_awaitable);

    // 2. mit try-catch
    try
    {
        std::size_t bytes_read = co_await    
            socket.async_read_some(boost::asio::buffer(buffer),  
                boost::asio::use_awaitable);
    }
    catch (const boost::system::system_error& e)
    {
        std::cerr << "Boost error during async_read_some: " << e.what() << 
            std::endl;
    }

    // 3. mit redirect_error CompletionToken
    boost::system::error_code ec;
    std::size_t bytes_read2 = co_await   
        socket.async_read_some(boost::asio::buffer(buffer),  
            boost::asio::redirect_error(boost::asio::use_awaitable, ec));
    if (ec)
    {
         std::cerr << "Error using error_code: " << ec.message() << std::endl;
    }

    // 4. mit as_tuple CompletionToken
    auto [ec2, bytes_read3] = co_await    
        socket.async_read_some(boost::asio::buffer(buffer),      
            boost::asio::as_tuple(boost::asio::use_awaitable));
    if (ec2)
    {
        std::cerr << "Error using as_tuple: " << ec2.message() << std::endl;
    }
}

Liste 7: Boost.Asio'da farklı hata işleme türlerine örnekler.


Yayımlandı

kategorisi

yazarı:

Etiketler:

Yorumlar

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir