2018 年 6 月

磁碟區 33 編號 6

本文章是由機器翻譯。

C + +-有效非同步協同程式和 C + + WinRT

Kenny Kerr |2018 年 6 月

Windows 執行階段在概念上,要在 Windows 執行階段的其他所有項目具有相對較簡單的非同步模型,它會著重於讓元件來公開非同步方法,並且讓它成為簡單的應用程式呼叫這些非同步方法。它沒有本身提供並行執行階段或甚至在建置組塊進行任何項目產生或取用非同步方法。相反地,所有作業,就以個別語言投影。這是因為它應該是並不想要忽略的 Windows 執行階段非同步模式。它是任何催生正確實作此模式。當然,這也表示的非同步 Windows 執行階段中的開發人員的感覺非常大量受到開發人員所選擇的語言。開發人員都只有在使用 C + + /CX 可能,例如錯誤但理解假設該非同步熱混亂。

C# 開發人員最理想的並行處理架構將會不同於 c + + 開發人員的理想的並行存取程式庫。語言和在 c + + 程式庫的角色是機制的負責處理的非同步模式,並提供自然橋接器的特定語言實作。

協同程式是慣用的抽象概念,實作和 c + + 呼叫非同步方法,但是讓我們先確定我們了解非同步模型的運作方式。假設有一個類別,以與單一的靜態方法,看起來像這樣:

struct Sample
{
  Sample() = delete;
  static Windows::Foundation::IAsyncAction CopyAsync();
};

非同步方法的結尾"Async"依照慣例,因此您可以將此視為複製非同步方法。可能封鎖或同步的替代方式,只需呼叫複製。傳入呼叫端可能會想封鎖的複製方法,以供在背景執行緒,皆無法承受前任出現 [沒有回應封鎖 UI 執行緒所使用的非封鎖,或非同步,方法是。

首先,似乎相當簡單呼叫 CopyAsync 方法。我可以撰寫下列 c + + 程式碼:

IAsyncAction async = Sample::CopyAsync();

您可以想像,產生 IAsyncAction 不是實際的最後結果,非同步方法,即使它是以傳統的程序性方式呼叫 CopyAsync 方法的結果。IAsyncAction 是呼叫端可能會等候結果時同步或非同步方式,視情況使用的物件。IAsyncAction,以及有三種其他已知介面,請遵循類似的模式,並提供不同的通訊資訊傳回到呼叫端被呼叫端的功能。中的資料表圖 1提供四個非同步介面的比較。

圖 1 的非同步介面的比較

名稱 結果 進度
IAsyncAction 不可 不可
IAsyncActionWithProgress 不可 希望
IAsyncOperation 希望 不可
IAsyncOperationWithProgress 希望 希望

在 c + + 詞彙中,介面可以表示中所示圖 2

圖 2 以 c + + 詞彙表示的非同步介面

namespace Windows::Foundation
{
  struct IAsyncAction;
  template <typename Progress>
  struct IAsyncActionWithProgress;
  template <typename Result>
  struct IAsyncOperation;
  template <typename Result, typename Progress>
  struct IAsyncOperationWithProgress;
}

IAsyncAction 和 IAsyncActionWithProgress 可以等候以判斷非同步方法完成,但不提供任何可觀察結果這些介面,或將其直接傳回值。IAsyncOperation 和 Iasyncoperationwithprogress<tresult,相反地,預期結果型別參數,以指出成功的非同步方法完成時可預期結果的型別。最後,IAsyncActionWithProgress 和 IAsyncOperationWithProgress 預期進行型別參數來指出應該定期長時間執行作業的非同步方法完成之前的進度資訊類型。

有幾種方式,要等候的非同步方法的結果時。我將不會說明這些所有這裡,會開啟此成很長的文章。雖然有很多種方式來處理非同步完成,只有兩個,建議您: async.get 方法,用於執行封鎖的等候,並協同程式的內容中執行合作式等候 co_await 非同步運算式。兩者都不是優於其他為它們只會有不同用途。讓我們看現在如何封鎖等候。

如前所述,就可以使用 get 方法,如下所示來達成封鎖等候:

IAsyncAction async = Sample::CopyAsync();
async.get();

很少的任何值中沒有非同步物件會保存,因此慣用的格式如下:

Sample::CopyAsync().get();

請務必記住 get 方法會封鎖呼叫執行緒,直到非同步方法完成。因此,不適合在 UI 執行緒上使用 get 方法,因為它可能會導致應用程式變成沒有回應。如果您嘗試這樣做,將會以未最佳化組建中引發判斷提示。Get 方法適合主控台應用程式或位置,無論基於任何理由,您可能不想使用協同程式的背景執行緒。

完成非同步方法時,get 方法會傳回任何結果直接呼叫端。若是 IAsyncAction 和 IAsyncActionWithProgress,傳回型別為 void。可用於非同步方法的起始檔案複製作業,但較不因此之類的非同步方法的讀取檔案的內容。讓我們加入另一個非同步方法的範例:

struct Sample
{
  Sample() = delete;
  static Windows::Foundation::IAsyncAction CopyAsync();
  static Windows::Foundation::IAsyncOperation<hstring> ReadAsync();
};

ReadAsync,在 get 方法會正確地轉送 hstring 結果給呼叫者作業完成之後:

Sample::CopyAsync().get();
hstring result = Sample::ReadAsync().get();

假設執行會傳回從 get 方法,產生的字串將會保留其成功完成時非同步方法傳回任何值。執行可能不會傳回,比方說,如果發生錯誤。

Get 方法會受到限制,因為它不能從 UI 執行緒,也不它利用完整的機器的並行存取,可能會因為保留呼叫端執行緒人質直到非同步方法完成為止。使用協同程式可讓非同步方法完成而不會保留這類寶貴的資源客群某些未定時間量。

處理非同步完成

您已經有控制代碼上非同步介面一般情況下,讓我們開始向下鑽研到他們在更多詳細資料中的運作方式。假設您不滿意封鎖等候 get 方法所提供,其他選項為何?我們將推出切換齒輪和焦點完全在協同程式但是,目前,我們探討這些非同步介面,以查看所提供。協同程式的支援,以及 get 方法,我看之前,依賴這些介面所隱含的合約和狀態機器。我將不會移到太多的詳細資料,因為您實際上不需要知道所有這些項目,但是讓我們來探索基本概念,讓他們至少必須熟悉如果您曾經需要深入了解和使用它們直接進行更多的事項。

所有四個非同步介面以邏輯方式都衍生 IAsyncInfo 介面。沒有您可以使用 IAsyncInfo 執行,而且即使存在因為它會加入額外負擔的位元慶祝的一個日子的極少。您應該實際考慮只有 IAsyncInfo 成員才是狀態,可以判斷您是否已完成之非同步方法,以及 [取消],可用來要求取消其結果已不再需要長時間執行作業。我 nitpick 這項設計,因為我喜歡非同步模式的一般情況下,並只想要它是完美的因為它是因此非常接近。

狀態成員可以是您需要判斷非同步方法是否已完成而不實際等候時相當實用。以下為範例:

auto async = ReadAsync();
if (async.Status() == AsyncStatus::Completed)
{
  auto result = async.GetResults();
  printf("%ls\n", result.c_str());
}

每個四個非同步介面,不 IAsyncInfo 本身,提供您已決定完成非同步方法之後,才應該呼叫 GetResults 方法的個別版本。請勿混淆這個使用 get 方法提供的 C + + WinRT。雖然 GetResults 實作非同步方法本身時,get 藉由 C + + WinRT。如果非同步方法仍在執行並將可能擲回例外狀況 hresult_illegal_method_call 如果提前呼叫,就不會封鎖 GetResults。毫無疑問,您可以開始想像封鎖 get 方法的實作方式。就概念而言,它看起來像這樣:

auto get() const
{
  if (Status() != AsyncStatus::Completed)
  {
    // Wait for completion somehow ...
  }
  return GetResults();
}

實際的實作會稍微複雜,但這會擷取它的 gist。此處的重點是,不論它是 IAsyncOperation,傳回值或 IAsyncAction,不會呼叫 GetResults。這是 GetResults 負責傳播可能會發生在非同步方法的實作中,將會重新擲回例外狀況所需的任何錯誤。

問題所保留下來,呼叫端如何等候完成。我要撰寫的非成員 get 函式,以顯示您的相關資訊。首先,我會使用這個基本大綱,先前的概念 get 方法所啟發:

template <typename T>
auto get(T const& async)
{
  if (async.Status() != AsyncStatus::Completed)
  {
    // Wait for completion somehow ...
  }
  return async.GetResults();
}

我想使用所有的四個非同步介面,所以我單方面使用 return 的陳述式的這個函式樣板。會以 c + + 語言 genericity 進行特殊的佈建,您可以針對該感謝。

四個非同步介面的每個提供唯一的已完成成員可以用來註冊回呼,稱為委派 —,將非同步方法完成時呼叫。在大部分的情況下,C + + WinRT 會自動建立您的委派。您只需要提供一些類似函式的處理常式,且是通常是最簡單的 lambda:

async.Completed([](auto&& async, AsyncStatus status)
{
  // It's done!
});

委派的第一個參數的型別是非同步的介面剛剛已完成,但是請記住以下幾點完成應該被視為簡單的訊號。也就是說,不會更一堆內已完成處理常式程式碼。基本上,您應該將它視為 noexcept 處理常式因為非同步方法不會自行得知要執行此處理常式內部發生任何失敗的工作。那麼您該怎麼做?

您可以只是通知等候執行緒使用的事件。圖 3顯示 get 函式可能的外觀。

圖 3 通知等候執行緒使用的事件

template <typename T>
auto get(T const& async)
{
  if (async.Status() != AsyncStatus::Completed)
  {
    handle signal = CreateEvent(nullptr, true, false, nullptr);
    async.Completed([&](auto&&, auto&&)
    {
      SetEvent(signal.get());
    });
    WaitForSingleObject(signal.get(), INFINITE);
  }
  return async.GetResults();
}

C + + WinRT 的取得方法,請使用條件變數與輕型的讀取器/寫入器鎖定,所以稍微更有效率。這類變數看起來可能如下所示的值圖 4

圖 4 條件變數使用輕型的讀取器/寫入器鎖定

template <typename T>
auto get(T const& async)
{
  if (async.Status() != AsyncStatus::Completed)
  {
    slim_mutex m;
    slim_condition_variable cv;
    bool completed = false;
    async.Completed([&](auto&&, auto&&)
    {
      {
        slim_lock_guard const guard(m);
        completed = true;
      }
      cv.notify_one();
    });
    slim_lock_guard guard(m);
    cv.wait(m, [&] { return completed; });
  }
  return async.GetResults();
}

如果您想要的話,您可以不用說,使用 c + + 標準程式庫的 mutex 和條件的變數。已完成處理常式會攔截非同步完成連接至,並可以完全以一般方式進行,只是重點。

當然,沒有理由您寫入您自己的 get 函式,而多個可能的協同程式將會是更為簡單且功能更多樣一般。儘管如此,希望這可協助您喜歡的部分電源與 Windows 執行階段中的彈性。

產生非同步物件

既然我們已瀏覽非同步介面,某些完成機制一般情況下,我們將我們注意到建立或產生這些四個非同步介面的實作。當您已經學會,實作 WinRT 介面使用 C + + WinRT 是非常簡單。中所示可能,比方說,實作 IAsyncAction,圖 5

圖 5 實作 IAsyncAction

struct MyAsync : implements<MyAsync, IAsyncAction, IAsyncInfo>
{
  // IAsyncInfo members ...
  uint32_t Id() const;
  AsyncStatus Status() const;
  HRESULT ErrorCode() const;
  void Cancel() const;
  void Close() const;
  // IAsyncAction members ...
  void Completed(AsyncActionCompletedHandler const& handler) const;
  AsyncActionCompletedHandler Completed() const;
  void GetResults() const;
};

難度是當您考慮您如何實作這些方法。雖然不難想像某些實作,則幾乎不可能不正確此第一個反向工程如何現有語言投影實際實作它們。您看到,WinRT 非同步模式只適用於如果每個人都實作這些介面使用非常特定的狀態機器,完全相同的方式。每個語言投影會進行相同的假設,有關如何實作此狀態機器,如果您實作稍有不同的方式,將會發生問題。

幸好,您不必擔心這,因為每個語言投影,除了 C + + /CX 中,已經實作這個方法正確了。以下是完整實作 IAsyncAction 由於 C + + WinRT 的協同程式支援:

IAsyncAction CopyAsync()
{
  co_return;
}

現在這不是特別有趣的實作,但卻非常教育和例子數量 C + WinRT 正在為您進行。因為這是完整的實作,我們可以使用它,才能施行一些什麼我們學到目前為止。Earlier CopyAsync 函數為協同程式。協同程式的傳回類型用來連結在一起的 IAsyncAction 和 IAsyncInfo,實作和 c + + 編譯器會使它生命右邊的目前。我們將更新版本中,瀏覽一些詳細資料,但現在讓我們觀察此協同程式的運作方式。請考慮下列的主控台應用程式:

IAsyncAction CopyAsync()
{
  co_return;
}
int main()
{
  IAsyncAction async = CopyAsync();
  async.get();
}

Main 函式呼叫傳回 IasyncAction CopyAsync 函式。如果您忘記了哪些 CopyAsync 函式的主體,或定義看起來像,它應該明顯,則只會傳回 IAsyncAction 物件是函式。因此您可以使用它,您已學到的所有方法中。

(這種) 協同程式必須 co_return 陳述式或 co_await 陳述式。它可能,當然有多個這類陳述式,但必須有至少下列其中一種才能實際上是為協同程式。如您所料,co_return 陳述式沒有採用任何種類的暫止或非同步。因此,此 CopyAsync 函式會產生 IAsyncAction 立即或同步方式完成。我可以說明這點,如下所示:

IAsyncAction Async()
{
  co_return;
}
int main()
{
  IAsyncAction async = Async();
  assert(async.Status() == AsyncStatus::Completed);
}

判斷提示會保證,則為 true。沒有任何的競爭。因為 CopyAsync 只是函式,呼叫端會封鎖,直到它傳回,而且它傳回的第一個機會剛好 co_return 陳述式。這表示,如果您有一些非同步合約,您需要實作,但實作不會實際需要導入任何非同步,則可以只傳回值,和情況下,直接封鎖或簡介內容切換。請考慮下載並快取的值,則會傳回的函式中所示圖 6

下載並快取的值會傳回圖 6 函式

hstring m_cache;
IAsyncOperation<hstring> ReadAsync()
{
  if (m_cache.empty())
  {
    // Download and cache value ...
  }
  co_return m_cache;
}
int main()
{
  hstring message = ReadAsync().get();
  printf("%ls\n", message.c_str());
}

第一次呼叫 ReadAsync 時,都可能是空的快取,並下載的結果。假定這將會擱置協同程式本身,而這會。暫止表示執行會傳回給呼叫者。呼叫端被交尚未,事實上,完成,因此需要以某種方式等候完成的非同步處理物件。

協同程式的優點是單一抽象概念,以便產生非同步物件和使用這些相同的非同步處理物件。應用程式開發介面或元件作者可能會實作非同步方法,如前文所述但 API 取用者或應用程式開發人員也可以使用協同程式呼叫並等候其完成。我們現在請重寫 main 函式,從圖 6等候使用協同程式:

IAsyncAction MainAsync()
{
  hstring result = co_await ReadAsync();
  printf("%ls\n", result.c_str());
}
int main()
{
  MainAsync().get();
}

基本上,我有執行舊的 main 函式的主體,變成 MainAsync 協同程式。Main 函式會使用 get 方法,以防止應用程式終止時以非同步方式完成協同程式。MainAsync 函式有新項目,而這是 co_await 陳述式。而不是使用 get 方法來封鎖呼叫執行緒,直到完成 ReadAsync,co_await 陳述式用來等候 ReadAsync 函式以合作式或非封鎖方式完成。這是我想暫停點的值。Co_await 陳述式表示暫停點。此應用程式只會呼叫 ReadAsync 一次,但您可以想像更有趣的應用程式中多次呼叫它。取得呼叫它的第一次 MainAsync 協同程式將實際暫停,並將控制權傳回給呼叫端。第二次呼叫時,它將不會暫停所有但而不是直接傳回值。

協同程式的新手非常許多 c + + 開發人員,因此不要覺得這仍看起來而魔法如果不正確。這些概念將會變成當您開始撰寫和偵錯協同程式自行很清楚。好消息是您已經知道足以有效地運用取用的非同步應用程式開發介面由 Windows 所提供的協同程式會開始。例如,您應該能夠原因有關中的主控台應用程式圖 7的運作方式。

圖 7 協同程式的範例主控台應用程式

#include "winrt/Windows.Web.Syndication.h"
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;
IAsyncAction MainAsync()
{
  Uri uri(L"https://kennykerr.ca/feed");
  SyndicationClient client;
  SyndicationFeed feed = co_await client.RetrieveFeedAsync(uri);
  for (auto&& item : feed.Items())
  {
    hstring title = item.Title().Text();
    printf("%ls\n", title.c_str());
  }
}
int main()
{
  init_apartment();
  MainAsync().get();
}

試試看現在並查看 [它是在 Windows 上使用現代 c + + 多少很有趣。

協同程式和執行緒集區

建立基本的協同程式只是一般。您可以非常輕鬆地 co_await 一些其他的非同步動作或作業,只要 co_return 值,或製作兩者的一些組合。以下是根本不是非同步的協同程式:

IAsyncOperation<int> return_123()
{
  co_return 123;
}

即使以同步方式執行,它仍會產生完全有效 IAsyncOperation 介面的實作:

int main()
{
  int result = return_123().get();
  assert(result == 123);
}

以下是將等候 5 秒後再傳回值的其中一個:

using namespace std::chrono;
IAsyncOperation<int> return_123_after_5s()
{
  co_await 5s;
  co_return 123;
}

下一個明顯要以非同步方式執行,尚未 main 函式會維持大致保持不變,這點受惠封鎖的 get 函式的行為:

int main()
{
  int result = return_123_after_5s().get();
  assert(result == 123);
}

因為 co_await 運算式是使用執行緒集區計時器 chrono 持續 Windows 執行緒集區,也會執行最後的協同程式中的 co_return 陳述式。Co_await 陳述式表示暫停點,且應明顯協同程式可能會在完全不同的執行緒暫停之後繼續。您也可以進行此明確使用 resume_background:

IAsyncOperation<int> background_123()
{
  co_await resume_background();
  co_return 123;
}

沒有任何明顯的延遲這次,但協同程式保證可以繼續在執行緒集區。如果您不確定嗎?您可能會快取的值,而且只是要如果必須從潛在的儲存體擷取值,將內容切換會導入。這是很好記住的協同程式也是函式,因此所有的一般規則適用:

IAsyncOperation<int> background_123()
{
  static std::atomic<int> result{0};
  if (result == 0)
  {
    co_await resume_background();
    result = 123;
  }
  co_return result;
}

這僅有條件地將介紹並行存取。多個執行緒可以偷竊比賽,呼叫 background_123,造成其中一些會繼續在執行緒集區中,但最終 primed 不可部分完成的變數,並協同程式開始同步完成。也就是,最壞的情況。

我們假設的值可能只能從儲存體讀取,一旦引發信號,指出這個值準備好。我們可以使用兩個的協同程式中圖 8來提取此。

圖 8 引發信號之後,從儲存體讀取值

handle m_signal{ CreateEvent(nullptr, true, false, nullptr) };
std::atomic<int> m_value{ 0 };
IAsyncAction prepare_result()
{
  co_await 5s;
  m_value = 123;
  SetEvent(m_signal.get());
}
IAsyncOperation<int> return_on_signal()
{
  co_await resume_on_signal(m_signal.get());
  co_return m_value;
}

第一個協同程式以人為方式等候 5 秒、 設定值,且再發出信號 Win32 事件。第二個協同程式 wait-events 等待事件發出訊號,然後只再傳回值。同樣地,執行緒集區用來等候事件,導致有效率且可擴充的實作。協調兩個協同程式相當直接:

int main()
{
  prepare_result();
  int result = return_on_signal().get();
  assert(result == 123);
}

Main 函式第一個協同程式就能啟動,但不會封鎖而等待其完成。第二個協同程式會立即開始等候的值,封鎖這麼做。

協同程式與呼叫內容

到目前為止,我著重在執行緒集區,或可能所謂的背景執行緒。C + + WinRT 熱愛 Windows 執行緒集區,但不會改變您需要取得回前景執行緒代表某些使用者互動的工作。讓我們看看精確的控制執行內容的方法。

協同程式可用於採用並行存取或處理的其他應用程式開發介面中的延遲,因為可能對於給定的協同程式在任何特定時間點的執行內容的時間會發生一些混淆。讓我們來清除幾件事。

圖 9示範會印出呼叫執行緒的一些基本資訊的簡單函式。

圖 9 列印出呼叫執行緒的基本資訊

void print_context()
{
  printf("thread:%d apartment:", GetCurrentThreadId());
  APTTYPE type;
  APTTYPEQUALIFIER qualifier;
  HRESULT const result = CoGetApartmentType(&type, &qualifier);
  if (result == S_OK)
  {
    puts(type == APTTYPE_MTA ? "MTA" : "STA");
  }
  else
  {
    puts("N/A");
  }
}

這是不是獨占或絕對的傾印 apartment 的詳細資訊,但它現在夠好,基於我們的目的。對於 COM nerds 那裡,n/A 是 「 不適用 」,以及您想要沒有其他 NA。有兩個主要的 apartment 模型的重新叫用。處理程序有最多一個多執行緒的 apartment (MTA) 並且可能有任意數目的單一執行緒 apartment (STA)。Apartment 都是不幸的現實,設計來容納 COM 的遠端處理架構,但在傳統上已用來支援不具備執行緒安全的 COM 物件。

STA com 用於確保永遠只會從正在建立的執行緒呼叫物件。當然,這表示某些機制來封送處理回入 apartment 執行緒其他執行緒的呼叫。Sta 通常會使用這個訊息迴圈或發送器佇列。MTA com 用於表示沒有執行緒相似性存在,但該封送處理時需要一些其他 apartment 源自呼叫。我將會節省的另一個的一天,所以複雜,[物件與執行緒之間的關聯性。理想的狀況時,物件可讓您即時它是敏捷式軟體開發且因此遭受 apartment 親和性。

讓我們使用 print_context 函式來撰寫一些有趣的程式。以下是呼叫 print_context 之前和之後使用 resume_background 移入 (執行緒集區) 的背景執行緒工作的其中一個:

IAsyncAction Async()
{
  print_context();
  co_await resume_background();
  print_context();
}

也請考慮下列呼叫:

int main()
{
  init_apartment();
  Async().get();
}

呼叫者很重要的因為它會判斷協同程式 (或任何函式) 的原始或呼叫內容。在此情況下,從主要沒有任何引數呼叫 init_apartment。這表示應用程式的主要執行緒會將聯結 MTA。因此,它不需要訊息迴圈或任何種類的發送器。這也表示執行緒可以愉快地保存他們封鎖執行因為此處的作法等候完成協同程式中使用封鎖的 get 函式。由於呼叫端執行緒 MTA 執行緒,協同程式開始在 MTA 上的執行。Resume_background co_await 運算式用來暫時暫停協同程式,而呼叫的執行緒釋放回呼叫端本身的協同程式免費一旦執行緒是可從執行緒集區,以便協同程式可能會繼續若要同時執行會繼續。一次 (亦稱為背景執行緒) 在執行緒集區,print_context 再次呼叫。如果您要執行此程式可能會看到的內容如下:

thread:18924 apartment:MTA
thread:9568 apartment:MTA

執行緒識別項不重要。重點在於它們是唯一。請注意,執行緒集區暫時提供執行緒也是 MTA 執行緒。這可以是如果我沒有在該執行緒上呼叫 init_apartment?如果您逐步執行 print_context 函式,您會發現 APTTYPEQUALIFIER 區分這些執行緒,並識別為隱含的執行緒關聯。您可以安全地假設執行緒集區執行緒在實務上,MTA 執行緒程序會保持 MTA 透過其他方式提供。

關鍵在於 resume_background co_await 運算式,會有效地切換為協同程式的執行內容的執行緒集區,不論哪一個執行緒或 apartment 原本執行。它必須不會封鎖呼叫端,並確保某些計算繫結作業之前,可能會封鎖呼叫執行緒暫停的協同程式評估,應該假設為協同程式。這不表示該 resume_background 應該一律使用。協同程式可能只是為了彙總其他集合的協同程式存在。在此情況下,協同程式中的任何 co_await 運算式可能會提供必要的暫止,以確保呼叫執行緒不封鎖。

現在請思考一下會發生什麼情況變更呼叫端,如果應用程式的主要功能,如下所示:

int main()
{
  init_apartment(apartment_type::single_threaded);
  Async();
  MSG message;
  while (GetMessage(&message, nullptr, 0, 0))
  {
    DispatchMessage(&message);
  }
}

這看起來不夠合理的。主要執行緒會成為 STA 執行緒、 呼叫非同步函式 (而不以任何方式封鎖),並進入訊息迴圈,以確保可以 STA 的跨 apartment 呼叫服務。問題在於,MTA 尚未建立,因此在執行緒集區所擁有的執行緒不會隱含地加入 MTA,因此不能使用的 COM 服務,例如啟用。如果您要立即執行程式可能會看到的內容如下:

thread:17552 apartment:STA
thread:19300 apartment:N/A

請注意下列 resume_background 暫止,協同程式不會再執行依賴 COM 執行階段程式碼所在的 apartment 內容。如果您真的主執行緒需要 STA,解決這個問題是輕鬆地確保 MTA 的 「 永遠開啟 」 無論如何,如下所示圖 10

圖 10 確保 MTA 是 「 永遠開啟 」

int main()
{
  CO_MTA_USAGE_COOKIE mta{};
  check_hresult(CoIncrementMTAUsage(&mta));
  init_apartment(apartment_type::single_threaded);
  Async();
  MSG message;
  while (GetMessage(&message, nullptr, 0, 0))
  {
    DispatchMessage(&message);
  }
}

CoIncrementMTAUsage 函式可確保建立的 MTA。呼叫執行緒直到,或明確的選擇會加入其他 apartment,在此情況下,會變成 MTA 之隱含成員。如果我要再次執行程式,我會收到所需的結果:

thread:11276 apartment:STA
thread:9412 apartment:MTA

這是本質上的環境中,大多數最新型的 Windows 應用程式尋找本身,但現在您的位元進一步了解如何控制,或自行建立該環境。加入我下一次如我們繼續瀏覽協同程式。

總結

好,我已經很深上非同步處理和 c + + 中的協同程式。一定會有多個並行的看法。它是這類有趣的主題,而且希望本簡介將協助您興奮時開始使用 C + 在 c + + 應用程式和元件,以及在您可以單單只有電源的非同步處理是簡單 + WinRT。


Kenny Kerr 作者、 系統程式設計師以及建立者的 C + + WinRT。他也是 Windows 團隊版其中他取消簽章的 c + + 為 Windows,讓開發人員驚人輕鬆撰寫美麗高效能的分散式應用程式和元件未來的 microsoft 工程師。