다음을 통해 공유


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: