Fast and Fluid czyli synchroniczne i asynchroniczne wywołania w Windows RT
W WinRT większość metod w obiektach jest przygotowana do wywołania asynchronicznego. Przy tworzeniu własnych komponentów również zachęcałbym do tworzenia par Metoda() oraz MetodaAsync()
W takim przypadku, gdy nasza metoda normalnie wyglądała by tak: int foo(int bar) - jej wersja asynchroniczna w warstwie deklaracji zmieniła by postać w następująca: IAsyncOperation<int> fooAsync(int bar) .
Jeśli nasza metoda nie miała by nic zwracać zamiast IAsyncOperation<void> możemy wykorzystać alternatywny interfejs: IAsyncAction fooAsync(int bar) dla odpowiednio wyglądającej metody void foo(int bar) .
To oczywiście semantyka C++. W C# jest to ładnie opakowane w słowem kluczowym async. Analogiczna konstrukcja metody foo w c# wyglądała by następująco:
async int foo(int bar) {} lub async void foo(int bar) {}
Wtedy pomimo deklaracji danego typu do zwrotu przygotowany jest obiekt System.Threading.Tasks.Task<int> lub System.Threading.Tasks.Task.
Jeśli sami konstruujemy własną metodę z typową operacją asynchroniczną i w ramach tej metody chcielibyśmy wywołać kilka innych operacji asynchronicznych, poczekać na ich wynik, aby nasz algorytm poprawnie zwrócił wynik, ale cały czas w tle, wtedy w języku C# możemy zastosować słowo klucz await, składnia wielce przyjemna i powodująca, że programista tak naprawdę nie widzi znaczącej różnicy w kodzie synchronicznym i asynchronicznym. Oto przykład:
async public int fooAsync(int bar)
{
SomeOtherObject obj = new SomeOtherObject();
await obj.firstCallAsync();
var intval = await obj.giveMeIntAsync();
return intvalue;
}
Taka składnia nie jest niestety dostępna z perspektywy C++. W C++ przypominam mamy dostępne interfejsy IAsyncOperation<T> oraz IAsyncAction. W obu przypadkach istotne są dla nas dwa elementy związane z rezultatem zleconego zadania. Jeden z nich to metoda GetResults() . W przypadku IAsyncOperation zwraca T, w przypadku IAsyncAction zwraca void, co jest specyficznym kuriozum przyznam, żeby widzieć metodę void GetResults() . Druga metoda jest krytyczna dla obu przypadków, jest to właściwość Completed następującego typu AsyncOperationCompletedHandler<TResult>^.
Te interfejsy podobnie jak w przypadku C# zwracają opakowane obiekty pochodzące z Concurrency Runtime (PPL). Jeśli tworzymy własną metodę asynchroniczną w C++ (naprzykład na potrzeby własnego komponentu WInRT do wykorzystania w innych językach) z Concurrency Runtime i biblioteką PPL wypadało by się zaprzyjaźnić. Poniżej podam tylko prosty przykład jak analogiczny fooAsync z C# skonstruować w C++. Najpierw synchroniczne wywołanie operacji asynchroncznej, czyli symulacja słówka kluczowego await:
template <typename TResult>
TResult sync_call(Windows::Foundation::IAsyncOperation<TResult>^ asyncOp)
{
Concurrency::event synchronizer;
while( 0 != synchronizer.wait(10))
{
if(asyncOp->Status == Windows::Foundation::AsyncStatus::Completed)
return asyncOp->GetResults();
if (asyncOp->Status ==Windows::Foundation::AsyncStatus::Error)
//todo: tutaj ustaw właściwy dla siebie sposób obsługi błędów
// podczas wykonania operacji asynchronicznej
throw std::exception("Sync call failed");
}
return asyncOp->GetResults();
}
wtedy nasza operacja synchroniczna, która chciała by wywołać operację asynchroniczną również w sposób synchroniczny wyglądałaby następująco:
int foo(int bar)
{
SomeOtherObject^ obj = ref new SomeOtherObject();
try {
sync_call(obj->firstCallAsync());
auto intval = sync_call(obj.giveMeIntAsync());
return intvalue;
} catch (...) {
return -1;
}
}
Połowa sukcesu. Teraz jak nasze zadanie wrzucić w tło i jako całość wywołać asynchronicznie? Załóżmy sobie konstrukcję gotowego obiektu i wyeksponowane w nim tylko elementy co istotniejsze dla naszego zadania:
public ref class FooContainer
{
public:
int foo(int bar) { }
IAsyncOperation<int> fooAsync(int bar) {}
private:
};
Jak wygląda foo, już wiemy. Jego wywołanie asynchroniczne to odpowiednie opakowanie w metodzie fooAsync, która powinna wyglądać następująco:
IAsyncOperation<int> fooAsync(int bar) {
auto fooTask = create_async([=]() -> int { return foo(bar); });
return fooTask;
}
Proste i czytelne prawda. Oczywiście to tylko banalny przykład. Komplet informacji w temacie jest dostępny tutaj:
- Concurrency namespace
- PPL i Concurrency Engine jest dostepny także dla klasycznego programowania w C++ (Win32, Visual Studio 2010), więcej informacji tutaj.
- Podobny artykuł po angielsku, napisany przez kolegów zza wielkiej kałuży.
- Artykuł na Windows 8 Dev Center: