C++/WinRT ile eşzamanlılık ve eşzamansız işlemler

Önemli

Bu konu başlığı altında, hem kullanıcı arabiriminizde hem de kullanıcı arabirimi olmayan uygulamalarınızda kullanmanızı önerdiğimiz coroutinesco_awaitve kavramları tanıtılmaktadır. Kolaylık olması için, bu giriş konu başlığındaki kod örneklerinin çoğunda konsol uygulaması (C++/WinRT) projeleri Windows gösterilmektedir. Bu konudaki sonraki kod örneklerinde coroutine'ler kullanılır; ancak pratik olması açısından, konsol uygulaması örneklerinde de uygulamanın çıktısını yazdırmayı tamamlamadan çıkmaması için, çıkmadan hemen önce engelleyici get işlev çağrısı kullanılmaya devam edilir. Bunu bir UI iş parçacığından yapmazsınız (yani engelleyen get işlevini çağırmayı). Bunun yerine co_await ifadesini kullanacaksınız. Kullanıcı arabirimi uygulamalarınızda kullanacağınız teknikler , Gelişmiş eşzamanlılık ve zaman uyumsuzluğ konu başlığında açıklanmıştır.

Giriş niteliğindeki bu konu, C++/WinRT ile Windows Çalışma Zamanı zaman uyumsuz nesnelerini hem oluşturabileceğiniz hem de kullanabileceğiniz bazı yolları gösterir. Bu konuyu okuduktan sonra, özellikle kullanıcı arabirimi uygulamalarınızda kullanacağınız teknikler açısından, ayrıca Gelişmiş eşzamanlılık ve zaman uyumsuzluk konusuna da bakın.

Asenkron işlemler ve Windows Çalışma Zamanı "Async" işlevleri

Tamamlanması 50 milisaniyeden uzun sürme olasılığı olan tüm Windows Çalışma Zamanı API'leri, zaman uyumsuz bir işlev (adı "Async" ile biten) olarak uygulanır. Eşzamansız bir işlevin uygulaması, çalışmayı başka bir iş parçacığında başlatır ve eşzamansız işlemi temsil eden bir nesneyle hemen geri döner. Zaman uyumsuz işlem tamamlandığında, döndürülen nesne çalışmadan kaynaklanan herhangi bir değer içerir. Windows::Foundation Windows Çalışma Zamanı ad alanı dört tür zaman uyumsuz işlem nesnesi içerir.

Bu zaman uyumsuz işlem türlerinin her biri winrt::Windows::Foundation C++/WinRT ad alanında karşılık gelen bir türe yansıtılır. C++/WinRT ayrıca dahili bir await bağdaştırıcısı yapısı da içerir. Doğrudan kullanmazsınız, ancak bu yapı sayesinde, bu zaman uyumsuz işlem türlerinden birini döndüren herhangi bir işlevin sonucunu birlikte beklemek için bir co_await deyim yazabilirsiniz. Ayrıca bu türleri döndüren kendi eşyordamlarınızı yazabilirsiniz.

Bir asenkron Windows işlevine örnek, SyndicationClient::RetrieveFeedAsync işlevidir; bu işlev, IAsyncOperationWithProgress<TResult, TProgress> türünde bir asenkron işlem nesnesi döndürür.

Böyle bir API’yi çağırmak için C++/WinRT’yi kullanmanın bazı yollarına bakalım: önce bloklayarak, sonra da bloklamadan. Temel fikirlerin çizimi için, sonraki birkaç kod örneğinde bir Windows Konsol Uygulaması (C++/WinRT) projesi kullanacağız. Kullanıcı arabirimi uygulaması için daha uygun teknikler Gelişmiş eşzamanlılık ve zaman uyumsuzluğ bölümünde ele alınıyor.

Çağrı iş parçacığını engelleme

Aşağıdaki kod örneği, RetrieveFeedAsync'ten bir eşzamansız işlem nesnesi alır ve eşzamansız işlemin sonuçları kullanılabilir hale gelene kadar çağıran iş parçacığını engellemek için bu nesne üzerinde get yöntemini çağırır.

Bu örneği doğrudan bir Windows Konsol Uygulaması (C++/WinRT) projesinin ana kaynak kodu dosyasına kopyalayıp yapıştırmak istiyorsanız, önce proje özelliklerinde Önceden Derlenmiş Üst Bilgileri Kullanma seçeneğini belirleyin.

// main.cpp
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Web.Syndication.h>

using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;

void ProcessFeed()
{
    Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
    SyndicationClient syndicationClient;
    SyndicationFeed syndicationFeed{ syndicationClient.RetrieveFeedAsync(rssFeedUri).get() };
    // use syndicationFeed.
}

int main()
{
    winrt::init_apartment();
    ProcessFeed();
}

get çağrısı, kod yazmayı kolaylaştırır ve herhangi bir nedenle eş yordam kullanmak istemeyebileceğiniz konsol uygulamaları veya arka plan iş parçacıkları için idealdir. Ancak ne eşzamanlıdır ne de asenkrondur; bu nedenle bir UI iş parçacığı için uygun değildir (ve böyle bir iş parçacığında kullanmayı denerseniz, optimizasyonsuz derlemelerde bir doğrulama tetiklenir). İşletim sistemi iş parçacıklarını başka yararlı işler yapmaktan alıkoymamak için farklı bir tekniğe ihtiyacımız var.

Bir coroutine yazma

C++/WinRT, C++ eş yordamlarını programlama modeliyle tümleştirerek bir sonucu işbirliğiyle beklemenin doğal bir yolunu sağlar. Bir coroutine yazarak kendi Windows Çalışma Zamanı zaman uyumsuz işleminizi oluşturabilirsiniz. Aşağıdaki kod örneğinde ProcessFeedAsync, eş yordamdır.

Note

Get işlevi, C++/WinRT projeksiyon türü winrt::Windows::Foundation::IAsyncAction üzerinde bulunur, böylece işlevi herhangi bir C++/WinRT projesinden çağırabilirsiniz. get, IAsyncAction türünde gerçek Windows Çalışma Zamanı uygulama ikili arabirimi (ABI) yüzeyinin parçası olmadığından işlevi IAsyncAction arabiriminin bir üyesi olarak listelenmiş olarak bulamazsınız.

// main.cpp
#include <iostream>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Web.Syndication.h>

using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;

void PrintFeed(SyndicationFeed const& syndicationFeed)
{
    for (SyndicationItem const& syndicationItem : syndicationFeed.Items())
    {
        std::wcout << syndicationItem.Title().Text().c_str() << std::endl;
    }
}

IAsyncAction ProcessFeedAsync()
{
    Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
    SyndicationClient syndicationClient;
    SyndicationFeed syndicationFeed{ co_await syndicationClient.RetrieveFeedAsync(rssFeedUri) };
    PrintFeed(syndicationFeed);
}

int main()
{
    winrt::init_apartment();

    auto processOp{ ProcessFeedAsync() };
    // do other work while the feed is being printed.
    processOp.get(); // no more work to do; call get() so that we see the printout before the application exits.
}

Bir coroutine, askıya alınabilen ve devam ettirilebilen bir işlevdir. Yukarıdaki ProcessFeedAsync eş yordamında, co_await ifadesine gelindiğinde eş yordam, RetrieveFeedAsync çağrısını asenkron olarak başlatır; ardından hemen kendini askıya alır ve denetimi çağırana geri verir (bu da yukarıdaki örnekte main'dir). main, akış alınırken ve yazdırılırken çalışmaya devam edebilir. Bu tamamlandığında ( RetrieveFeedAsync çağrısı tamamlandığında), ProcessFeedAsync coroutine sonraki deyimde devam eder.

Bir eş yordamı diğer eş yordamlara birleştirebilirsiniz. İsterseniz get to block çağrısı yapabilir ve tamamlanmasını bekleyebilirsiniz (ve varsa sonucu alabilirsiniz). Veya Windows Çalışma Zamanı destekleyen başka bir programlama diline geçirebilirsiniz.

Delegeler kullanılarak eşzamansız eylem ve işlemlerin tamamlanma ve/veya ilerleme olayları da işlenebilir. Ayrıntılar ve kod örnekleri için bkz. Zaman uyumsuz eylemler ve işlemler için temsilci türleri.

Gördüğünüz gibi, yukarıdaki kod örneğinde, main'den çıkmadan hemen önce bloklayan get işlev çağrısını kullanmaya devam ediyoruz. Ama bu yalnızca uygulamanın çıktısını yazdırmayı bitirmeden önce kapanmaması içindir.

Bir Windows Çalışma Zamanı türünü eşzamansız olarak döndür

Bu sonraki örnekte, belirli bir URI için RetrieveFeedAsync çağrısını sarmalayarak, bize eşzamansız olarak SyndicationFeed döndüren bir RetrieveBlogFeedAsync işlevi elde ederiz.

// main.cpp
#include <iostream>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Web.Syndication.h>

using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;

void PrintFeed(SyndicationFeed const& syndicationFeed)
{
    for (SyndicationItem const& syndicationItem : syndicationFeed.Items())
    {
        std::wcout << syndicationItem.Title().Text().c_str() << std::endl;
    }
}

IAsyncOperationWithProgress<SyndicationFeed, RetrievalProgress> RetrieveBlogFeedAsync()
{
    Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
    SyndicationClient syndicationClient;
    return syndicationClient.RetrieveFeedAsync(rssFeedUri);
}

int main()
{
    winrt::init_apartment();

    auto feedOp{ RetrieveBlogFeedAsync() };
    // do other work.
    PrintFeed(feedOp.get());
}

Yukarıdaki örnekte RetrieveBlogFeedAsync , hem ilerleme hem de dönüş değeri olan bir IAsyncOperationWithProgress döndürür. RetrieveBlogFeedAsync işini yaparken ve akışı alırken başka işler de yapabiliriz. Ardından, engellenmesi için bu zaman uyumsuz işlem nesnesine get çağrısı yapıyoruz, tamamlanmasını bekliyoruz ve ardından işlemin sonuçlarını elde ediyoruz.

Eşzamansız olarak bir Windows Çalışma Zamanı türü döndürüyorsanız, IAsyncOperation<TResult> veya IAsyncOperationWithProgress<TResult, TProgress> döndürmelisiniz. Herhangi bir birinci taraf veya üçüncü taraf çalışma zamanı sınıfı ya da bir Windows Çalışma Zamanı işlevine geçirilebilen veya bu işlevden döndürülebilen herhangi bir tür (örneğin, int, veya winrt::hstring) bu kapsama girer. Bu zaman uyumsuz işlem türlerinden birini Windows Çalışma Zamanı olmayan bir türle kullanmaya çalışırsanız, derleyici "T WinRT türü olmalıdır" hatasıyla size yardımcı olur.

Bir eş yordamda en az bir co_await deyim yoksa, eş yordam olarak nitelenmesi için en az bir co_return veya bir co_yield deyimi olmalıdır. Coroutine’inizin herhangi bir eşzamansızlık oluşturmadan ve dolayısıyla engelleme ya da bağlam değiştirme olmadan bir değer döndürebildiği durumlar olacaktır. İşte bunu, bir değeri önbelleğe alıp ikinci ve sonraki çağrılışlarda yapan bir örnek.

winrt::hstring m_cache;

IAsyncOperation<winrt::hstring> ReadAsync()
{
    if (m_cache.empty())
    {
        // Asynchronously download and cache the string.
    }
    co_return m_cache;
}

Windows-Runtime olmayan bir türü eşzamansız olarak döndürün

Windows Çalışma Zamanı türü olmayan bir türü zaman uyumsuz olarak döndürüyorsanız, Paralel Desenler Kitaplığı (PPL) concurrency::task döndürmelisiniz. concurrency::task kullanmanızı öneriyoruz, çünkü std::future'dan daha iyi performans ve ileriye dönük olarak daha iyi uyumluluk sağlar.

Tavsiye

<pplawait.h> öğesini eklerseniz, concurrency::task'i bir eş yordam türü olarak kullanabilirsiniz.

// main.cpp
#include <iostream>
#include <ppltasks.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Web.Syndication.h>

using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;

concurrency::task<std::wstring> RetrieveFirstTitleAsync()
{
    return concurrency::create_task([]
        {
            Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
            SyndicationClient syndicationClient;
            SyndicationFeed syndicationFeed{ syndicationClient.RetrieveFeedAsync(rssFeedUri).get() };
            return std::wstring{ syndicationFeed.Items().GetAt(0).Title().Text() };
        });
}

int main()
{
    winrt::init_apartment();

    auto firstTitleOp{ RetrieveFirstTitleAsync() };
    // Do other work here.
    std::wcout << firstTitleOp.get() << std::endl;
}

Parametre aktarımı

Zaman uyumlu işlevler için varsayılan olarak parametreleri kullanmanız const& gerekir. Bu, kopyaların ek yükünü önler (başvuru sayımını içerir ve bu da birbirine bağlı artışlar ve azalmalar anlamına gelir).

// Synchronous function.
void DoWork(Param const& value);

Ancak bir coroutine'e referans parametresi geçirirseniz sorunlarla karşılaşabilirsiniz.

// NOT the recommended way to pass a value to a coroutine!
IASyncAction DoWorkAsync(Param const& value)
{
    // While it's ok to access value here...

    co_await DoOtherWorkAsync(); // (this is the first suspension point)...

    // ...accessing value here carries no guarantees of safety.
}

Bir eş yordamda yürütme, denetimin çağırana geri verildiği ve çağrı çerçevesinin kapsam dışına çıktığı ilk askıya alma noktasına kadar eşzamanlıdır. Coroutine yeniden çalışmaya başladığında, bir başvuru parametresinin işaret ettiği kaynak değerin başına her şey gelmiş olabilir. Coroutine’in bakış açısından, bir referans parametresinin yaşam süresi denetlenemez. Yani, yukarıdaki örnekte, co_await olana kadar value öğesine güvenle erişebiliriz, ancak ondan sonra erişemeyiz. value çağıran tarafından yok edilirse, bundan sonra coroutine içinde ona erişilmeye çalışılması bellek bozulmasına yol açar. Ayrıca, bu işlevin askıya alınıp daha sonra devam ettikten sonra value’yu kullanmaya çalışması gibi bir risk varsa, value’yu DoOtherWorkAsync’e de güvenli bir şekilde geçiremeyiz.

Parametrelerin askıya alınıp yeniden başlatıldıktan sonra güvenle kullanılabilmesi için, eşyordamlarınız varsayılan olarak değerle geçirme kullanmalı; böylece parametreleri değer olarak yakalar ve yaşam süresi sorunlarını önler. Bunun güvenli olduğundan emin olduğunuz için bu kılavuzdan sapabileceğiniz durumlar nadir olacaktır.

// Coroutine
IASyncAction DoWorkAsync(Param value); // not const&

Değerle geçirme, argümanın taşınmasının veya kopyalanmasının fazla maliyetli olmamasını gerektirir; bu da genellikle akıllı işaretçiler için geçerlidir.

Sabit değere göre geçirmenin (değeri taşımak istemiyorsanız) iyi bir uygulama olduğu da tartışılabilir. Kopya oluşturduğunuz kaynak değer üzerinde herhangi bir etkisi olmaz, ancak amacı netleştirir ve yanlışlıkla kopyayı değiştirmenize yardımcı olur.

// coroutine with strictly unnecessary const (but arguably good practice).
IASyncAction DoWorkAsync(Param const value);

Ayrıca bkz. Standart diziler ve vektörler; bu, standart vektörün zaman uyumsuz bir çağrılan işleve nasıl geçirileceği konusunu ele alır.

Coroutine imzanızı değiştiremiyorsanız ancak uygulamayı değiştirebiliyorsanız, ilk co_awaitkopyadan önce yerel bir kopya oluşturabilirsiniz.

IASyncAction DoWorkAsync(Param const& value)
{
    auto safe_value = value;
    // It's ok to access both safe_value and value here.

    co_await DoOtherWorkAsync();

    // It's ok to access only safe_value here (not value).
}

Param kopyalamak pahalıysa, ilk co_await'den önce yalnızca ihtiyacınız olan parçaları ayıklayın.

IASyncAction DoWorkAsync(Param const& value)
{
    auto safe_data = value.data;
    // It's ok to access safe_data, value.data, and value here.

    co_await DoOtherWorkAsync();

    // It's ok to access only safe_data here (not value.data, nor value).
}

Sınıf üyesi bir eş yordamda this işaretçisine güvenli biçimde erişim

Bkz. C++/WinRT'de güçlü ve zayıf başvurular.

Önemli API'ler