Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
Bu konuda, C++/WinRT kapsamında eşzamanlılık ve zaman uyumsuzluğunun gelişmiş senaryoları açıklanmaktadır.
Bu konuya giriş için önce Eşzamanlılık ve zaman uyumsuz işlemler konusunu okuyun.
İşi Windows iş parçacığı havuzuna aktarma
Coroutine, diğer işlevler gibi bir işlevdir; çünkü bir işlev yürütmeyi çağırana geri döndürene kadar çağıran engellenir. Ve, bir eş yordamın dönebilmesi için ilk fırsat, ilk co_await, co_returnveya co_yield'dir.
Bu nedenle, bir eş yordamda işlemle ilişkili çalışma gerçekleştirmeden önce, arayanın engellenmemesi için çağırana yürütmeyi (başka bir deyişle, bir askıya alma noktası ekleme) döndürmeniz gerekir. Bunu başka bir işlemi co_awaityaparak yapmıyorsanız co_await işlevini . Bu, denetimi çağırana döndürür ve ardından iş parçacığı havuzundaki bir iş parçacığında çalışmaya hemen devam eder.
Uygulamada kullanılan iş parçacığı havuzu alt düzey Windows iş parçacığı havuzu olduğundan en iyi şekilde verimlidir.
IAsyncOperation<uint32_t> DoWorkOnThreadPoolAsync()
{
co_await winrt::resume_background(); // Return control; resume on thread pool.
uint32_t result;
for (uint32_t y = 0; y < height; ++y)
for (uint32_t x = 0; x < width; ++x)
{
// Do compute-bound work here.
}
co_return result;
}
İş parçacığı bağlılığını göz önünde bulundurarak programlama
Bu senaryo bir önceki senaryoya göre genişler. Bazı işleri iş parçacığı havuzuna boşaltabilirsiniz, ancak ardından ilerleme durumunu kullanıcı arabiriminde (UI) görüntülemek istersiniz.
IAsyncAction DoWorkAsync(TextBlock textblock)
{
co_await winrt::resume_background();
// Do compute-bound work here.
textblock.Text(L"Done!"); // Error: TextBlock has thread affinity.
}
Yukarıdaki kod bir winrt::hresult_wrong_thread istisnası oluşturur, çünkü bir TextBlock onu oluşturan iş parçacığından güncellenmelidir ve bu da UI iş parçacığıdır. Çözümlerden biri, coroutine'mizin ilk olarak çağrıldığı iş parçacığı bağlamını yakalamaktır. Bunu yapmak için bir winrt::apartment_context nesnesi örneği oluşturun, arka plan görevi yapın ve ardından co_await ile çağıran bağlama geri geçmek için .
IAsyncAction DoWorkAsync(TextBlock textblock)
{
winrt::apartment_context ui_thread; // Capture calling context.
co_await winrt::resume_background();
// Do compute-bound work here.
co_await ui_thread; // Switch back to calling context.
textblock.Text(L"Done!"); // Ok if we really were called from the UI thread.
}
Yukarıdaki coroutine, TextBlock'i oluşturan UI iş parçacığından çağrıldığı sürece, bu teknik çalışır. Uygulamanızda bundan emin olduğunuz birçok durum olacaktır.
Kullanıcı arabirimini güncellemek için hangi iş parçacığını çağırdığınızdan emin olmadığınız durumları kapsayan daha genel bir çözüm için, belirli bir ön plan iş parçacığına geçiş yapmak üzere co_await işlevini . Aşağıdaki kod örneğinde, TextBlock ile ilişkili dağıtıcı nesnesini geçirerek (Dispatcher özelliğine erişerek) ön plan iş parçacığını belirteceğiz.
winrt::resume_foreground uygulaması, coroutine'de ondan sonra gelen işi yürütmek için bu dağıtıcı nesnesinde CoreDispatcher.RunAsync çağırır.
IAsyncAction DoWorkAsync(TextBlock textblock)
{
co_await winrt::resume_background();
// Do compute-bound work here.
// Switch to the foreground thread associated with textblock.
co_await winrt::resume_foreground(textblock.Dispatcher());
textblock.Text(L"Done!"); // Guaranteed to work.
}
winrt::resume_foreground işlevi isteğe bağlı bir öncelik parametresi alır. Bu parametreyi kullanıyorsanız, yukarıda gösterilen desen uygundur. Değilse, co_await winrt::resume_foreground(someDispatcherObject);'ı sadece co_await someDispatcherObject;olarak basitleştirmeyi seçebilirsiniz.
Yürütme bağlamları, bir eş yordamda devam etme ve geçiş yapma
Genel olarak, bir korutindaki bir askıya alma noktasından sonra, orijinal yürütme iş parçacığı devre dışı kalabilir ve herhangi bir iş parçacığında devam etme gerçekleşebilir (başka bir deyişle, herhangi bir iş parçacığı zaman uyumsuz işlem için Tamamlandı yöntemini çağırabilir).
Dört Windows Çalışma Zamanı asenkron işlem türünden birini (co_await) yaparsanız, C++/WinRT co_awaitnoktasında çağrı bağlamını yakalar. Ayrıca, devam devam ettiğinde hala bu bağlamda olduğunuzdan emin olursunuz. C++/WinRT, zaten arama bağlamında olup olmadığınızı denetleyerek ve değilse buna geçiş yaparak bunu yapar.
co_awaitöncesinde tek iş parçacıklı apartman (STA) iş parçacığında iseniz, sonrasında da aynı iş parçacığında olursunuz; co_awaitöncesinde çok iş parçacıklı bir apartman (MTA) iş parçacığında iseniz, sonrasında da bir iş parçacığında olursunuz.
IAsyncAction ProcessFeedAsync()
{
Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
SyndicationClient syndicationClient;
// The thread context at this point is captured...
SyndicationFeed syndicationFeed{ co_await syndicationClient.RetrieveFeedAsync(rssFeedUri) };
// ...and is restored at this point.
}
Bu davranışa güvenebilmenizin nedeni, C++/WinRT'nin bu Windows Çalışma Zamanı zaman uyumsuz işlem türlerini C++ coroutine dil desteğine uyarlamak için kod sağlamasıdır (bu kod parçaları bekleme bağdaştırıcıları olarak adlandırılır). C++/WinRT'de kalan beklenebilir türler yalnızca iş parçacığı havuzu sarmalayıcıları ve/veya yardımcılarından ibarettir; dolayısıyla, iş parçacığı havuzunda tamamlanırlar.
using namespace std::chrono_literals;
IAsyncOperation<int> return_123_after_5s()
{
// No matter what the thread context is at this point...
co_await 5s;
// ...we're on the thread pool at this point.
co_return 123;
}
C++/WinRT eş yordam uygulaması içinde başka bir türde co_await yaparsanız, bağdaştırıcıları başka bir kitaplık sağlar ve bu bağdaştırıcıların devam ettirme ve bağlamlar açısından ne yaptığını anlamanız gerekir.
Bağlam geçişlerini en düşük düzeyde tutmak için bu konuda daha önce gördüğümüz tekniklerden bazılarını kullanabilirsiniz. Şimdi bunu yapmanın bazı çizimlerini görelim. Bu sonraki sahte kod örneğinde, bir görüntüyü yüklemek için Windows Çalışma Zamanı API'sini çağıran, bu görüntüyü işlemek için bir arka plan iş parçacığına düşen ve ardından görüntüyü kullanıcı arabiriminde görüntülemek için ui iş parçacığına geri dönen bir olay işleyicisinin ana hattını göstereceğiz.
IAsyncAction MainPage::ClickHandler(IInspectable /* sender */, RoutedEventArgs /* args */)
{
// We begin in the UI context.
// Call StorageFile::OpenAsync to load an image file.
// The call to OpenAsync occurred on a background thread, but C++/WinRT has restored us to the UI thread by this point.
co_await winrt::resume_background();
// We're now on a background thread.
// Process the image.
co_await winrt::resume_foreground(this->Dispatcher());
// We're back on MainPage's UI thread.
// Display the image in the UI.
}
Bu senaryo için StorageFile::OpenAsync çağrısında biraz verimsizlik vardır. C++/WinRT, kullanıcı arabirimi iş parçacığı bağlamını geri yüklemek üzere devam ettikten sonra, işleyicinin yürütmeyi çağırana geri döndürebilmesi için arka plan iş parçacığına gerekli bir bağlam değişikliği yapar. Ancak bu durumda, kullanıcı arabirimini güncelleştirmek üzere olana kadar kullanıcı arabirimi iş parçacığında olmanız gerekmez. çağrımızdan önce ne kadar çok Windows Çalışma Zamanı API'sini
IAsyncAction MainPage::ClickHandler(IInspectable /* sender */, RoutedEventArgs /* args */)
{
// We begin in the UI context.
co_await winrt::resume_background();
// We're now on a background thread.
// Call StorageFile::OpenAsync to load an image file.
// Process the image.
co_await winrt::resume_foreground(this->Dispatcher());
// We're back on MainPage's UI thread.
// Display the image in the UI.
}
Daha gelişmiş bir şey yapmak istiyorsanız kendi await bağdaştırıcılarınızı yazabilirsiniz. Örneğin, asenkron eylemin tamamlandığı aynı iş parçacığında bir co_await'ın devam etmesini istiyorsanız (yani, bağlam değişikliği olmuyor), aşağıda gösterilenlere benzer await adaptörleri yazarak başlayabilirsiniz.
Uyarı
Aşağıdaki kod örneği yalnızca eğitim amacıyla verilmiştir; await bağdaştırıcılarının nasıl çalıştığını anlamaya başlamanızı sağlamaktır. Bu tekniği kendi kod tabanınızda kullanmak istiyorsanız kendi await bağdaştırıcı yapılarınızı geliştirmenizi ve test etmenizi öneririz. Örneğin, complete_on_any, complete_on_currentve complete_on(dispatcher)şeklinde yazabilirsiniz. Ayrıca IAsyncXxx türünü şablon parametresi olarak alan şablonlar oluşturmayı da göz önünde bulundurun.
struct no_switch
{
no_switch(Windows::Foundation::IAsyncAction const& async) : m_async(async)
{
}
bool await_ready() const
{
return m_async.Status() == Windows::Foundation::AsyncStatus::Completed;
}
void await_suspend(std::experimental::coroutine_handle<> handle) const
{
m_async.Completed([handle](Windows::Foundation::IAsyncAction const& /* asyncInfo */, Windows::Foundation::AsyncStatus const& /* asyncStatus */)
{
handle();
});
}
auto await_resume() const
{
return m_async.GetResults();
}
private:
Windows::Foundation::IAsyncAction const& m_async;
};
no_switch await bağdaştırıcılarının nasıl kullanılacağını anlamak için öncelikle C++ derleyicisi bir co_await ifadeyle karşılaştığında await_ready, await_suspend ve await_resume adlı işlevleri aradığını bilmeniz gerekir. C++/WinRT kitaplığı, varsayılan olarak makul davranışlar elde edebilmeniz için bu işlevleri sağlar.
IAsyncAction async{ ProcessFeedAsync() };
co_await async;
no_switch await bağdaştırıcılarını kullanmak için bu co_await ifadesinin türünü IAsyncXxx yerine no_switcholarak değiştirin.
IAsyncAction async{ ProcessFeedAsync() };
co_await static_cast<no_switch>(async);
Ardından C++ derleyicisi, IAsyncXxxile eşleşen üç await_xxx işlevini aramak yerine, no_switchile eşleşen işlevleri arar.
winrt::resume_foreground üzerine derinlemesine bir inceleme
C++/WinRT 2.0
Geçerli davranış, yığın geri sarma ve yeniden kuyruğa alma işlemine güvenebileceğiniz anlamına gelir; ve bu, özellikle düşük düzeyli sistem kodunda sistem kararlılığı için önemlidir. Yukarıdaki iş parçacığı bağımlılığını göz önünde bulundurarak Programlamabölümündeki son kod listesi, bir arka plan iş parçacığında karmaşık hesaplamalar gerçekleştirmeyi ve ardından kullanıcı arabirimini (UI) güncelleştirmek için uygun UI iş parçacığına geçmeyi gösterir.
winrt::resume_foreground içsel olarak şu şekilde görünür.
auto resume_foreground(...) noexcept
{
struct awaitable
{
bool await_ready() const
{
return false; // Queue without waiting.
// return m_dispatcher.HasThreadAccess(); // The C++/WinRT 1.0 implementation.
}
void await_resume() const {}
void await_suspend(coroutine_handle<> handle) const { ... }
};
return awaitable{ ... };
};
Bu geçerli ve önceki davranış, Win32 uygulama geliştirmesinde PostMessage ile SendMessage arasındaki farka benzer. PostMessage işi kuyruğa alır ve sonra işin tamamlanmasını beklemeden yığını geri alır. Yığın geri sarma gerekli olabilir.
winrt::resume_foreground işlevi başlangıçta yalnızca Windows 10'un öncesinde tanıtılan CoreDispatcher'ı (CoreWindow'a bağlı) desteklemektedir. O zamandan beri daha esnek ve verimli bir dağıtıcı tanıttık: DispatcherQueue. Kendi amaçlarınız için DispatcherQueue oluşturabilirsiniz. Bu basit konsol uygulamasını düşünün.
using namespace Windows::System;
winrt::fire_and_forget RunAsync(DispatcherQueue queue);
int main()
{
auto controller{ DispatcherQueueController::CreateOnDedicatedThread() };
RunAsync(controller.DispatcherQueue());
getchar();
}
Yukarıdaki örnek, özel bir iş parçacığında bir kuyruk (denetleyici içinde yer alır) oluşturur ve ardından denetleyiciyi coroutine fonksiyonuna iletir. Coroutine, özel iş parçacığında beklemek (askıya alma ve sürdürme) için kuyruğu kullanabilir. DispatcherQueue'in bir diğer yaygın kullanım alanı, geleneksel bir masaüstü veya Win32 uygulamasında, geçerli kullanıcı arabirimi iş parçacığında bir kuyruk oluşturmaktır.
DispatcherQueueController CreateDispatcherQueueController()
{
DispatcherQueueOptions options
{
sizeof(DispatcherQueueOptions),
DQTYPE_THREAD_CURRENT,
DQTAT_COM_STA
};
ABI::Windows::System::IDispatcherQueueController* ptr{};
winrt::check_hresult(CreateDispatcherQueueController(options, &ptr));
return { ptr, take_ownership_from_abi };
}
Bu, denetleyiciyi oluşturmak için Win32 stili createDispatcherQueueController işlevini çağırarak ve ardından sonuçta elde edilen kuyruk denetleyicisinin sahipliğini çağırana WinRT nesnesi olarak aktararak Win32 işlevlerini C++/WinRT projelerinize nasıl çağırabileceğinizi ve birleştirebileceğinizi gösterir. Mevcut Petzold stili Win32 masaüstü uygulamanızda verimli ve sorunsuz kuyruğa alma işlemini de tam olarak bu şekilde destekleyebilirsiniz.
winrt::fire_and_forget RunAsync(DispatcherQueue queue);
int main()
{
Window window;
auto controller{ CreateDispatcherQueueController() };
RunAsync(controller.DispatcherQueue());
MSG message;
while (GetMessage(&message, nullptr, 0, 0))
{
DispatchMessage(&message);
}
}
Yukarıdaki basit ana işlev bir pencere oluşturarak başlar. Bunun bir pencere sınıfı kaydettiğinizi ve en üst düzey masaüstü penceresini oluşturmak için CreateWindow'u çağırdığını düşünebilirsiniz. CreateDispatcherQueueController işlevi, bu denetleyicinin sahip olduğu dağıtıcı kuyruğunu kullanarak bazı coroutine'ler çağrılmadan önce kuyruk denetleyicisini oluşturmak için çağrılır. Daha sonra geleneksel bir ileti pompası girilir ve bu yazışmada koroutin doğal olarak sürdürülmesi gerçekleşir. Bunu yaptıktan sonra, uygulamanızda asenkron veya mesaja dayalı iş akışınız için eş yordamların zarif dünyasına geri dönebilirsiniz.
winrt::fire_and_forget RunAsync(DispatcherQueue queue)
{
... // Begin on the calling thread...
co_await winrt::resume_foreground(queue);
... // ...resume on the dispatcher thread.
}
winrt::fire_and_forget RunAsync(DispatcherQueue queue)
{
...
co_await winrt::resume_foreground(queue, DispatcherQueuePriority::High);
...
}
Veya varsayılan kuyruğa alma sırasını kullanarak.
...
#include <winrt/Windows.System.h>
using namespace Windows::System;
...
winrt::fire_and_forget RunAsync(DispatcherQueue queue)
{
...
co_await queue;
...
}
Uyarı
Yukarıda gösterildiği gibi, -ing co_awaitolduğunuz türün ad alanı için projeksiyon üst bilgisini eklediğinizden emin olun. Örneğin, Windows::UI::Core::CoreDispatcher, Windows::System::DispatcherQueueveya Microsoft::UI::Dispatching::DispatcherQueue.
Ya da bu durumda kuyruk kapatmayı algılama ve düzgün bir şekilde işleme.
winrt::fire_and_forget RunAsync(DispatcherQueue queue)
{
...
if (co_await queue)
{
... // Resume on dispatcher thread.
}
else
{
... // Still on calling thread.
}
}
co_await ifadesi, dağıtıcı iş parçacığında devam etmenin gerçekleşeceğini belirten truedöndürür. Başka bir deyişle, kuyruğa alma başarılı oldu. Buna karşılık, kuyruğun denetleyicisi kapatılmak üzere olduğu ve artık kuyruk isteklerine hizmet etmediği için yürütmenin hâlâ çağıran iş parçacığında kaldığını belirtmek amacıyla false döndürür.
Bu nedenle, C++/WinRT'yi eş yordamlarla birleştirdiğinizde elinizin altında çok büyük bir güce sahip oluyorsunuz; ve özellikle geleneksel Petzold tarzı masaüstü uygulama geliştirirken.
Zaman uyumsuz bir işlemi iptal etme ve iptalle ilgili geri çağırmalar
Windows Çalışma Zamanı'nın asenkron programlama özellikleri, devam etmekte olan bir asenkron eylemi veya işlemi iptal etmenizi sağlar. Burada, büyük olabilecek bir dosya koleksiyonunu almak için StorageFolder::GetFilesAsync çağrısında bulunan ve sonuçta elde edilen zaman uyumsuz işlem nesnesini bir veri üyesinde depolayan bir örnek verilmiştir. Kullanıcının işlemi iptal etme seçeneği vardır.
// MainPage.xaml
...
<Button x:Name="workButton" Click="OnWork">Work</Button>
<Button x:Name="cancelButton" Click="OnCancel">Cancel</Button>
...
// MainPage.h
...
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Storage.Search.h>
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::Storage;
using namespace Windows::Storage::Search;
using namespace Windows::UI::Xaml;
...
struct MainPage : MainPageT<MainPage>
{
MainPage()
{
InitializeComponent();
}
IAsyncAction OnWork(IInspectable /* sender */, RoutedEventArgs /* args */)
{
workButton().Content(winrt::box_value(L"Working..."));
// Enable the Pictures Library capability in the app manifest file.
StorageFolder picturesLibrary{ KnownFolders::PicturesLibrary() };
m_async = picturesLibrary.GetFilesAsync(CommonFileQuery::OrderByDate, 0, 1000);
IVectorView<StorageFile> filesInFolder{ co_await m_async };
workButton().Content(box_value(L"Done!"));
// Process the files in some way.
}
void OnCancel(IInspectable const& /* sender */, RoutedEventArgs const& /* args */)
{
if (m_async.Status() != AsyncStatus::Completed)
{
m_async.Cancel();
workButton().Content(winrt::box_value(L"Canceled"));
}
}
private:
IAsyncOperation<::IVectorView<StorageFile>> m_async;
};
...
İptal işleminin uygulama tarafı için basit bir örnekle başlayalım.
// main.cpp
#include <iostream>
#include <winrt/Windows.Foundation.h>
using namespace winrt;
using namespace Windows::Foundation;
using namespace std::chrono_literals;
IAsyncAction ImplicitCancelationAsync()
{
while (true)
{
std::cout << "ImplicitCancelationAsync: do some work for 1 second" << std::endl;
co_await 1s;
}
}
IAsyncAction MainCoroutineAsync()
{
auto implicit_cancelation{ ImplicitCancelationAsync() };
co_await 3s;
implicit_cancelation.Cancel();
}
int main()
{
winrt::init_apartment();
MainCoroutineAsync().get();
}
Yukarıdaki örneği çalıştırırsanız , ImplicitCancelationAsync'in saniyede üç saniye boyunca bir ileti yazdırdığını ve ardından iptal edilmenin bir sonucu olarak otomatik olarak sonlandırıldığını görürsünüz. Bu, işe yarar çünkü bir co_await ifadeyle karşılaştığında, bir korotin iptal edilip edilmediğini denetler. Varsa, kısa devre yapar; ve değilse, normal şekilde askıya alınır.
İptal işlemi, coroutine askıya alınırken elbette gerçekleşebilir. Ancak coroutine devam ettiğinde veya başka bir co_await'ye ulaştığında, iptal durumunu kontrol eder. Bu sorun, iptale yanıt vermede büyük olasılıkla çok karmaşık bir gecikme süresinden biridir.
Bu nedenle, başka bir seçenek de coroutine'iniz içinden açıkça iptal durumunu sorgulamaktır. Yukarıdaki örneği aşağıdaki listede yer alan kodla güncelleştirin. Bu yeni örnekte , ExplicitCancelationAsyncwinrt::get_cancellation_token işlevi tarafından döndürülen nesneyi alır ve coroutine'nin iptal edilip edilmediğini düzenli aralıklarla denetlemek için kullanır. İptal edilmediği sürece, coroutine sonsuza dek bir döngüde devam eder; iptal edildikten sonra ise döngü ve işlev normal şekilde sona erer. Sonuç önceki örnekle aynıdır, ancak burada çıkış açıkça ve denetim altında gerçekleşir.
IAsyncAction ExplicitCancelationAsync()
{
auto cancelation_token{ co_await winrt::get_cancellation_token() };
while (!cancelation_token())
{
std::cout << "ExplicitCancelationAsync: do some work for 1 second" << std::endl;
co_await 1s;
}
}
IAsyncAction MainCoroutineAsync()
{
auto explicit_cancelation{ ExplicitCancelationAsync() };
co_await 3s;
explicit_cancelation.Cancel();
}
...
winrt::get_cancellation_token
İptal geri çağırmasını kaydetme
Windows Çalışma Zamanı'nın iptali, otomatik olarak diğer eşzamanlı olmayan nesnelere aktarılmaz. Ancak, Windows SDK'sının 10.0.17763.0 (Windows 10, sürüm 1809) sürümünde kullanıma sunulan bir iptal geri çağırması kaydedebilirsiniz. İptalin yayılabilmesini sağlayan ve mevcut eşzamanlılık kütüphaneleriyle bütünleşmeyi mümkün kılan önleyici bir kancadır.
Bu sonraki kod örneğinde NestedCoroutineAsync işi yapar, ancak içinde özel bir iptal mantığı yoktur. CancelationPropagatorAsync, iç içe geçmiş korotini saran bir sarmalayıcıdır; sarmalayıcı, iptal işlemini önceden iletir.
// main.cpp
#include <iostream>
#include <winrt/Windows.Foundation.h>
using namespace winrt;
using namespace Windows::Foundation;
using namespace std::chrono_literals;
IAsyncAction NestedCoroutineAsync()
{
while (true)
{
std::cout << "NestedCoroutineAsync: do some work for 1 second" << std::endl;
co_await 1s;
}
}
IAsyncAction CancelationPropagatorAsync()
{
auto cancelation_token{ co_await winrt::get_cancellation_token() };
auto nested_coroutine{ NestedCoroutineAsync() };
cancelation_token.callback([=]
{
nested_coroutine.Cancel();
});
co_await nested_coroutine;
}
IAsyncAction MainCoroutineAsync()
{
auto cancelation_propagator{ CancelationPropagatorAsync() };
co_await 3s;
cancelation_propagator.Cancel();
}
int main()
{
winrt::init_apartment();
MainCoroutineAsync().get();
}
CancellationPropagatorAsync, bir lambda işlevini kendi iptal geri çağırması için kaydeder ve tâbi görev tamamlanana kadar bekler (askıya alınır). CancellationPropagatorAsync iptal edildiğinde veya eğer iptal edilirse, iptal işlemini iç içe geçmiş eşli yürütmeye iletilir. İptal için yoklama yapmanıza gerek yoktur; ve iptal süresiz olarak engellenmez. Bu mekanizma, yeterince esneklik sağlayarak C++/WinRT ile ilgili hiçbir şey bilmeyen bir eş yordam veya eşzamanlılık kitaplığıyla etkileşim kurmanıza olanak tanır.
İlerleme durumunu raporlama
Coroutine'iniz IAsyncActionWithProgress veya IAsyncOperationWithProgress döndürüyorsa, winrt::get_progress_token işlevi tarafından döndürülen nesneyi alabilir ve ilerleme durumunu bir ilerleme işleyicisine geri bildirmek için kullanabilirsiniz. İşte bir kod örneği.
// main.cpp
#include <iostream>
#include <winrt/Windows.Foundation.h>
using namespace winrt;
using namespace Windows::Foundation;
using namespace std::chrono_literals;
IAsyncOperationWithProgress<double, double> CalcPiTo5DPs()
{
auto progress{ co_await winrt::get_progress_token() };
co_await 1s;
double pi_so_far{ 3.1 };
progress.set_result(pi_so_far);
progress(0.2);
co_await 1s;
pi_so_far += 4.e-2;
progress.set_result(pi_so_far);
progress(0.4);
co_await 1s;
pi_so_far += 1.e-3;
progress.set_result(pi_so_far);
progress(0.6);
co_await 1s;
pi_so_far += 5.e-4;
progress.set_result(pi_so_far);
progress(0.8);
co_await 1s;
pi_so_far += 9.e-5;
progress.set_result(pi_so_far);
progress(1.0);
co_return pi_so_far;
}
IAsyncAction DoMath()
{
auto async_op_with_progress{ CalcPiTo5DPs() };
async_op_with_progress.Progress([](auto const& sender, double progress)
{
std::wcout << L"CalcPiTo5DPs() reports progress: " << progress << L". "
<< L"Value so far: " << sender.GetResults() << std::endl;
});
double pi{ co_await async_op_with_progress };
std::wcout << L"CalcPiTo5DPs() is complete !" << std::endl;
std::wcout << L"Pi is approx.: " << pi << std::endl;
}
int main()
{
winrt::init_apartment();
DoMath().get();
}
İlerleme durumunu bildirmek için ilerleme jetonunu, ilerleme değeriyle çağırın. Geçici bir sonuç ayarlamak için ilerleme belirtecinde yöntemini kullanın set_result() .
Uyarı
Geçici sonuçları raporlamak için C++/WinRT sürüm 2.0.210309.3 veya üzeri gerekir.
Yukarıdaki örnek, her ilerleme raporu için bir geçici sonuç ayarlamayı seçer. Herhangi bir durumda, istediğiniz zaman geçici sonuçları raporlamayı seçebilirsiniz. İlerleme raporuyla bir arada olması gerekmez.
Uyarı
Zaman uyumsuz bir eylem veya işlem için birden fazla tamamlama işleyicisi uygulamak doğru değildir. Tamamlanmış olay için ya tek bir temsilciniz olabilir ya da onu co_await edebilirsiniz. her ikisine de sahipseniz, ikincisi başarısız olur. Aşağıdaki iki tamamlama işleyicisi türünden biri uygundur; aynı eşzamansız nesne için ikisi birden değil.
auto async_op_with_progress{ CalcPiTo5DPs() };
async_op_with_progress.Completed([](auto const& sender, AsyncStatus /* status */)
{
double pi{ sender.GetResults() };
});
auto async_op_with_progress{ CalcPiTo5DPs() };
double pi{ co_await async_op_with_progress };
Tamamlama işleyicileri hakkında daha fazla bilgi için bkz. zaman uyumsuz işlemler ve eylemler için temsilci türleri.
Ateşle ve unut
Bazen, diğer çalışmalarla eşzamanlı olarak gerçekleştirilebilecek bir göreviniz vardır ve bu görevin tamamlanmasını beklemeniz gerekmez (başka hiçbir çalışma buna bağımlı değildir) veya bir değer döndürmek için buna ihtiyacınız yoktur. Bu durumda, görevi atabilir ve unutabilirsiniz. Bunu, dönüş türü winrt::fire_and_forget olan bir korutin yazarak yapabilirsiniz (Windows Çalışma Zamanı zaman uyumsuz işlem türlerinden biri veya concurrency::taskyerine).
// main.cpp
#include <winrt/Windows.Foundation.h>
using namespace winrt;
using namespace std::chrono_literals;
winrt::fire_and_forget CompleteInFiveSeconds()
{
co_await 5s;
}
int main()
{
winrt::init_apartment();
CompleteInFiveSeconds();
// Do other work here.
}
winrt::fire_and_forget , içinde zaman uyumsuz işlemler gerçekleştirmeniz gerektiğinde olay işleyicinizin dönüş türü olarak da yararlıdır. Burada bir örnek verilmiştir (ayrıca bkz. C++/WinRT
winrt::fire_and_forget MyClass::MyMediaBinder_OnBinding(MediaBinder const&, MediaBindingEventArgs args)
{
auto lifetime{ get_strong() }; // Prevent *this* from prematurely being destructed.
auto ensure_completion{ unique_deferral(args.GetDeferral()) }; // Take a deferral, and ensure that we complete it.
auto file{ co_await StorageFile::GetFileFromApplicationUriAsync(Uri(L"ms-appx:///video_file.mp4")) };
args.SetStorageFile(file);
// The destructor of unique_deferral completes the deferral here.
}
İlk argüman (gönderen) asla kullanmadığımız için isimsiz bırakılır. Bu nedenle bunu referans olarak bırakmakta güvendeyiz. Ancak args değerinin geçirildiğini gözlemleyin. Yukarıdaki Parametre Geçirme bölümüne bakın.
Çekirdek saplayıcısı bekleniyor
C++/WinRT, bir çekirdek olayı sinyal verilene kadar askıya almak için kullanabileceğiniz bir winrt::resume_on_signal işlevi sağlar.
co_await resume_on_signal(h) dönene kadar tanıtıcının geçerli kalmasını sağlamak sizin sorumluluğunuzdadır.
resume_on_signal sizin için bunu yapamaz, çünkü bu ilk örnekte olduğu gibi resume_on_signal başlamadan önce bile tutamacı kaybetmiş olabilirsiniz.
IAsyncAction Async(HANDLE event)
{
co_await DoWorkAsync();
co_await resume_on_signal(event); // The incoming handle is not valid here.
}
Gelen HANDLE yalnızca işlev dönene kadar geçerlidir ve bu işlev (bir koroutin olan) ilk askıya alma noktasında (bu durumda ilk co_await) döner.
DoWorkAsyncbeklerken, kontrol çağırana geri döndü, çağırma çerçevesi kapsam dışı kaldı ve artık coroutine yeniden başladığında tanıtıcının geçerli olup olmayacağını bilmiyorsunuz.
Teknik olarak, koroutinimiz parametrelerini gerektiği gibi, değere göre alıyor (bkz. yukarıdaki Parametre Geçirme). Ancak bu durumda, o rehberliğin metnini değil
IAsyncAction Async(winrt::handle event)
{
co_await DoWorkAsync();
co_await resume_on_signal(event); // The incoming handle *is* valid here.
}
Bir winrt::handle değer olarak geçirmek, coroutine ömrü boyunca çekirdek tutamacının geçerli kalmasını sağlayan sahiplik semantiği sağlar.
Bu eş yordama şu şekilde hitap edebilirsiniz.
namespace
{
winrt::handle duplicate(winrt::handle const& other, DWORD access)
{
winrt::handle result;
if (other)
{
winrt::check_bool(::DuplicateHandle(::GetCurrentProcess(),
other.get(), ::GetCurrentProcess(), result.put(), access, FALSE, 0));
}
return result;
}
winrt::handle make_manual_reset_event(bool initialState = false)
{
winrt::handle event{ ::CreateEvent(nullptr, true, initialState, nullptr) };
winrt::check_bool(static_cast<bool>(event));
return event;
}
}
IAsyncAction SampleCaller()
{
handle event{ make_manual_reset_event() };
auto async{ Async(duplicate(event)) };
::SetEvent(event.get());
event.close(); // Our handle is closed, but Async still has a valid handle.
co_await async; // Will wake up when *event* is signaled.
}
Örnekte olduğu gibi, bir zaman aşımı değerini resume_on_signal'e geçirebilirsiniz.
winrt::handle event = ...
if (co_await winrt::resume_on_signal(event.get(), std::literals::2s))
{
puts("signaled");
}
else
{
puts("timed out");
}
Asenkron zaman aşımları kolaylaştırıldı
C++/WinRT, C++ eş yordamlarına büyük yatırımlar yapar. Eşzamanlılık kodu yazma üzerindeki etkisi dönüşümseldir. Bu bölümde, zaman uyumsuzlu ilişkin ayrıntıların önemli olmadığı durumlar anlatılır ve tek istediğiniz sonucun orada ve sonra olmasıdır. Bu nedenle C++/WinRT'nin IAsyncAction Windows Runtime zaman uyumsuz işlem arabirimini uygulaması, std::future tarafından sağlanana benzer bir get işlevine sahiptir.
using namespace winrt::Windows::Foundation;
int main()
{
IAsyncAction async = ...
async.get();
puts("Done!");
}
, zaman uyumsuz nesne tamamlarken işlevini süresiz olarak engeller. Zaman uyumsuz nesneler çok kısa ömürlü olma eğilimindedir, bu nedenle genellikle ihtiyacınız olan tek şey budur.
Ancak bunun yeterli olmadığı durumlar vardır ve bir süre geçtikten sonra beklemeyi bırakmanız gerekir. Bu kodu yazmak, Windows Çalışma Zamanı tarafından sağlanan yapı taşları sayesinde her zaman mümkün olmuştur. Ancak artık C++/WinRT , wait_for işlevini sağlayarak bunu çok daha kolay hale getiriyor. Ayrıca IAsyncAction üzerinde de uygulanır ve yine std::future tarafından sağlanana benzer.
using namespace std::chrono_literals;
int main()
{
IAsyncAction async = ...
if (async.wait_for(5s) == AsyncStatus::Completed)
{
puts("done");
}
}
Uyarı
wait_for arabirimde std::chrono::duration kullanır, ancak std::chrono::duration sağladığından daha küçük bir aralıkla sınırlıdır (kabaca 49,7 gün).
Bu sonraki örnekteki wait_for yaklaşık beş saniye bekler ve ardından tamamlamayı denetler. Karşılaştırma uygunsa, zaman uyumsuz nesnenin başarıyla tamamlandığını ve işlemi tamamladığınızı bilirsiniz. Bir sonuç bekliyorsanız, sonucu almak için GetResults yöntemine yapılan bir çağrıyla bunu takip edebilirsiniz.
Uyarı
wait_for ve get birbirini dışlar (ikisini birden çağıramazsınız).
int main()
{
IAsyncOperation<int> async = ...
if (async.wait_for(5s) == AsyncStatus::Completed)
{
printf("result %d\n", async.GetResults());
}
}
Zaman uyumsuz nesne o zamana kadar tamamlandığından , GetResults yöntemi daha fazla beklemeden sonucu hemen döndürür. Gördüğünüz gibi wait_for, eşzamanlı olmayan nesnenin durumunu döndürür. Bu nedenle, daha ayrıntılı denetim için kullanabilirsiniz, örneğin.
switch (async.wait_for(5s))
{
case AsyncStatus::Completed:
printf("result %d\n", async.GetResults());
break;
case AsyncStatus::Canceled:
puts("canceled");
break;
case AsyncStatus::Error:
puts("failed");
break;
case AsyncStatus::Started:
puts("still running");
break;
}
- AsyncStatus::Completed zaman uyumsuz nesnenin başarıyla tamamlandığı anlamına geldiğini ve herhangi bir sonucu almak için GetResults yöntemini çağırabileceğinizi unutmayın.
- AsyncStatus::Canceled, zaman uyumsuz nesnenin iptal edilmiş olduğunu belirtir. İptal genellikle çağıran tarafından istenir, bu nedenle bu durumu işlemek nadir olacaktır. Genellikle, iptal edilmiş bir zaman uyumsuz nesne basitçe atılır. İsterseniz iptal istisnasını tekrar atmak için GetResults yöntemini çağırabilirsiniz.
- AsyncStatus::Error, zaman uyumsuz nesnenin bir şekilde başarısız olduğunu belirtir. İsterseniz istisnayı tekrar fırlatmak için GetResults yöntemini çağırabilirsiniz.
- AsyncStatus::Started, zaman uyumsuz nesnenin hala çalışmakta olduğunu gösterir. Windows Çalışma Zamanı zaman uyumsuz deseni ne birden çok beklemeye ne de bekleyicilere izin verir. Bu, döngüde wait_for çağıramazsınız anlamına gelir. Eğer bekleme süresi gerçekten zaman aşımına uğradıysa, birkaç seçeneğiniz kalır. Nesneyi bırakabilir veya herhangi bir sonucu almak için GetResults yöntemini çağırmadan önce durumunu yoklayabilirsiniz. Ancak en iyisi bu noktada nesneyi atmaktır.
Alternatif bir yöntem, yalnızca Başlatıldıdurumunu kontrol etmek ve diğer durumlarla GetResults'un ilgilenmesine izin vermektir.
if (async.wait_for(5s) == AsyncStatus::Started)
{
puts("timed out");
}
else
{
// will throw appropriate exception if in canceled or error state
auto results = async.GetResults();
}
Diziyi asenkron olarak döndürme
Aşağıda, hata MIDL2025 oluşturan MIDL 3.0 örneği verilmiştir: [msg]söz dizimi hatası [bağlam]: > bekleniyor veya "["yakınında.
Windows.Foundation.IAsyncOperation<Int32[]> RetrieveArrayAsync();
Bunun nedeni, parametreli arabirimde parametre türü bağımsız değişkeni olarak dizi kullanmanın geçersiz olmasıdır. Bu nedenle, bir diziyi çalışma zamanı sınıf yönteminden zaman uyumsuz olarak geri geçirme hedefine ulaşmak için daha az belirgin bir yönteme ihtiyacımız vardır.
Bir PropertyValue nesnesine kutulanmış diziyi döndürebilirsiniz. Çağıran kod daha sonra onu kutudan çıkarır. Burada, SampleComponent çalışma zamanı sınıfını bir Windows Çalışma Zamanı Bileşeni (C++/WinRT) projesine ekleyip, ardından (örneğin) bunu bir Core App (C++/WinRT) projesinden kullanarak deneyebileceğiniz bir kod örneği verilmiştir.
// SampleComponent.idl
namespace MyComponentProject
{
runtimeclass SampleComponent
{
Windows.Foundation.IAsyncOperation<IInspectable> RetrieveCollectionAsync();
};
}
// SampleComponent.h
...
struct SampleComponent : SampleComponentT<SampleComponent>
{
...
Windows::Foundation::IAsyncOperation<Windows::Foundation::IInspectable> RetrieveCollectionAsync()
{
co_return Windows::Foundation::PropertyValue::CreateInt32Array({ 99, 101 }); // Box an array into a PropertyValue.
}
}
...
// SampleCoreApp.cpp
...
MyComponentProject::SampleComponent m_sample_component;
...
auto boxed_array{ co_await m_sample_component.RetrieveCollectionAsync() };
auto property_value{ boxed_array.as<winrt::Windows::Foundation::IPropertyValue>() };
winrt::com_array<int32_t> my_array;
property_value.GetInt32Array(my_array); // Unbox back into an array.
...
Önemli API'ler
- IAsyncAction arabirimi
- IAsyncActionWithProgress<TProgress> arabirim
- IAsyncOperation<TResult> arabirimi
- IAsyncOperationWithProgress [<TResult, TProgress]> arabirimi
- SyndicationClient::RetrieveFeedAsync yöntemi
- winrt::fire_and_forget
- winrt::iptal_tokenu_getir
- winrt::get_progress_token
- winrt::resume_foreground
İlgili konular
- Eşzamanlılık ve eşzamansız işlemler
- C++/WinRT'da temsilcileri kullanarak olayları işleme