本文章是由機器翻譯。

並行事件

有四種方法可以在 C++ 專案中使用並行事件

Rick Molloy

下載範例程式碼

通常被詢問如何將新的平行運算的 Visual Studio 2010 Beta 版的程式庫到現有的 C + + 專案的整合。 在此的資料行中我將說明幾個的平行模式程式庫 (PPL)、 非同步代理程式庫,並行執行階段您現有的專案中的部分類別與方法您可以使用 API。 我會逐步解說四個常見的案例開發人員面對多執行緒應用程式開發,並說明如何可以生產力權限就立即使用代理程式庫與 PPL 多執行緒的程式更有效率且更可調整。

其中一個:將從 UI 執行緒內,將工作移至背景工作

其中一項您正在告知以避免身為 Windows 開發人員的第一個項目溢出 UI 執行緒。 使用者的經驗的回應視窗非常是不論是否開發人員提供客戶等待滑鼠指標或 Windows 提供它們 hung UI frosted 玻璃視窗的形式。 我們正在提供本指南通常是而簡要的:不要在 UI 執行緒上執行任何封鎖的呼叫,但是而移動這些給背景執行緒呼叫。 我的經驗是,本指南並不足夠而且程式碼撰寫工作冗長乏味,容易發生錯誤,不是。

在此的情況下,我將提供一個簡單的序列範例,示範如何使用現有的執行緒 API 移動工作。 然後,我會提供兩種方法將工作移至背景執行緒使用 PPL 和代理程式庫。 我會藉由將回到細節範例的 UI 執行緒的換行設定這種情況。

將長時間執行的序列作業移至背景執行緒

因此什麼意思將工作移至背景執行緒? 如果已執行了長時間或可能會封鎖作業的一些功能,而我想要將該函式移至背景執行緒,好許多照本宣科的程式碼是否涉及機制的實際移動的工作,甚至的東西在單一函式一樣簡單呼叫例如以下所示:

void SomeFunction(int x, int y){
        LongRunningOperation(x, y);
}

首先,您要封裝任何要使用的狀態。 此處我只封裝設定一組的整數,因此我可以使用內建的容器,就像一個 std::vector、 一個的 std::pair 或一個的 std::tuple 但更通常我的過同仁執行自己的結構或如下的類別中的值的封裝:

struct LongRunningOperationParams{
        LongRunningOperationParams(int x_, int y_):x(x_),y(y_){}
        int x;
        int y;
}

您就必須建立比對執行緒集區或 CreateThread 簽章,unpackages 該狀態的全域或靜態函式 (通常是由解除參考一個 void * 指標) 執行此的函式],然後刪除資料,如果適當的話。 下面是一個範例:

DWORD WINAPI LongRunningOperationThreadFunc(void* data){
                LongRunningOperationParams* pData =
           (LongRunningOperationParams*) data;
                LongRunningOperation(pData->x,pData->y);
                //delete the data if appropriate
         delete pData;
}

現在您可以最後取得周圍實際排程執行緒與資料看起來會像這樣:

void SomeFunction(int x, int y){
        //package up our thread state
        //and store it on the heap
        LongRunningOperationParams* threadData =
        new  LongRunningOperationParams(x,y);
        //now schedule the thread with the data
        CreateThread(NULL,NULL,&LongRunningOperationThreadFunc,
          (void*) pData,NULL);
}

這可能會不似乎這樣更多的程式碼。 技術上,我已將只兩行程式碼新增至 SomeFunction、 四行的類別和執行緒函式的三行。 但這實際上是四次為多程式碼。 我們只是要排程具有兩個參數的單一函式呼叫的程式碼的 12 行發生從三行程式碼。 最後一次我必須要像這樣,我相信我必須擷取大約 8 個變數,而擷取和設定所有此狀態會變成很繁瑣和容易出錯。 如果我想起正確,我會找到並修正只在的程序擷取狀態,並建立建構函式中至少兩個錯誤。

我也還沒接觸上如何等待執行緒完成,這通常需要建立事件和 WaitForSingleObject 呼叫追蹤該控點,以及的就說清除控點,當您完成的。 這至少三個多個程式碼,行,並,仍將留出處理的例外狀況和傳回碼。

CreateThread 的替代方法:task_group 類別

我要說明第一個方法使用從 [PPL task_group 類別。 如果您不熟悉 task_group 類別,它會提供繁衍透過 task_group::run 非同步工作,等待透過 task_group::wait 完成其工作的方法。 它也提供取消尚未被尚未開始的任務,並包含封裝 std::exception_ptr 的例外狀況,並重新擲回它的功能。

您會看到明顯較少的程式碼是否涉及這裡與 CreateThread 方法從可讀性的觀點來看,是更接近序列的範例程式碼。 第一個步驟是建立 task_group 物件。 這個物件必須是儲存某處,可以管理它的存留期),就例如在堆積上或在類別中的成員變數。 接下來您可以使用 task_group::run 來排程執行工作的工作 (不是一個執行緒)。 Task_group::run 採用 functor,以做為參數,並讓您管理該 functor 的存留期。 使用 C + + 0x Lambda 封裝的狀態上這實際上是兩行變更程式。 以下是程式碼外觀如下:

//a task_group member variable
task_group backgroundTasks;
void SomeFunction(int x, int y){
        backgroundTasks.run([x,y](){LongRunningOperation(x, y);});
}

進行工作的非同步代理程式的程式庫

另一個方法是使用代理程式庫包含根據訊息傳遞的方法。 程式碼變更的量是關於相同,但是有主要的語意差別值得指出代理程式為基礎的方法。 而不是排程工作,您可以建置訊息傳遞管線,並以非同步方式傳送訊息,包含只是資料依賴管線處理訊息本身。 在先前的情況下我可以傳送一個訊息包含 x 和 y。 工作仍然會發生在其他的執行緒上,但進入佇列同一管線的後續呼叫,而且 (相較於 task_group,並不會提供排序保證) 的順序處理訊息。

首先,您必須包含訊息結構。 您可以在就其實使用相同的結構之前,但我將它重新命名如下所示:

struct LongRunningOperationMsg{
        LongRunningOperationMsg (int x, int y):m_x(x),m_y(y){}
        int m_x;
        int m_y;
}

是宣告一個位置來傳送訊息至下一個步驟。 在代理程式庫,訊息可傳送至任何訊息介面的 「 目標 」,但在此特定案例最適合呼叫 < T >。 呼叫 < T >接受郵件,並與一個 functor 訊息做為參數的建構。 宣告和呼叫的建構可能如下 (使用 lambdas) 所示:

call<LongRunningOperationMsg>* LongRunningOperationCall = new
   call<LongRunningOperationMsg>([]( LongRunningOperationMsg msg)
{
LongRunningOperation(msg.x, msg.y);
})

現在則稍有 SomeFunction 該修改。 目標是建構訊息,並以非同步方式將其傳送至呼叫物件。 會在個別執行緒上叫用呼叫,當收到訊息時:

void SomeFunction(int x, int y){
        asend(LongRunningOperationCall, LongRunningOperationMsg(x,y));
}

取得回至 UI 執行緒的工作

取得關閉 UI 執行緒的工作是僅有一半的問題。 想必 LongRunningOperation 結尾您將會得到一些有意義的結果以及下一步通常取得回至 UI 執行緒的工作。 採取的方式視您的應用程式,但最容易達到此目的所提供的 Visual Studio 2010 程式庫中的方法是從代理程式庫使用的 API 和訊息區塊的另一個組:try_receive 並 unbounded_buffer < T >。

一個 unbounded_buffer < T >可用來儲存含有資料和可能需要在 UI 執行緒上執行的程式碼。 Try_receive 是可以用來查詢是否要顯示的資料是 nonblocking API 呼叫。

就例如如果您在您的 UI 執行緒上呈現影像,您可以使用類似下列的程式碼取得回至 UI 執行緒上的資料後 InvalidateRect 呼叫:

unbounded_buffer<ImageClass>* ImageBuffer;
LONG APIENTRY MainWndProc(HWND hwnd, UINT uMsg,
  WPARAM wParam, LPARAM lParam)
{
    RECT rcClient;
    int i;
    ...
   ImageClass image;
   //check the buffer for images and if there is one there, display it.
   if (try_receive(ImageBuffer,image))
       DisplayImage(image);
   ...
}

已經在這裡,省略類似的訊息迴圈,實作一些詳細資訊,但我希望這一節的指示來示範的技巧。 我建議您檢查文件有完整的範例程式碼的每一個這種方法的工作範例。

圖 1 的非--安全執行緒類別

samclass
Widget{
size_t m_width;
size_t m_height;
public:
Widget(size_t w, size_t h):m_width(w),m_height(h){};
size_t GetWidth(){
return m_width;
}
size_t GetHeight(){
return m_height;
}
void SetWidth(size_t width){
m_width = width;
}
void SetHeight(size_t height){
m_height = height;
}
};

兩個:管理訊息區塊與代理程式的共用的狀態

在多執行緒應用程式開發中的另一個常見的情況管理共用的狀態。 特別,只要您嘗試進行通訊,或在執行緒之間共用資料,快速管理共用的狀態會變成您要處理的問題。 我通常還看過這個方法是要新增物件以保護其資料成員和公用的介面關鍵區段,但這很快就會變成維護問題,有時候會變得效能問題。 在此的情況下我會逐步解說使用鎖定,一個序列和天真無邪的範例,然後我會顯示從代理程式庫使用訊息區塊的替代方案]。

鎖定簡單的 Widget 類別

圖 1 顯示寬度和高度的資料成員變更其狀態的簡單方法與非安全執行緒 Widget 類別。

若要讓 Widget 類別執行緒安全最天真無邪的方法是保護它的方法與關鍵區段或讀取器-寫入器鎖定。 PPL 包含一個的 reader_writer_lock 並的圖 2 提供第一個明顯的解決方案天真無邪方法看:您可以使用 reader_writer_lock [PPL 中。

使用 reader_writer_lock 平行模式庫圖 2

class LockedWidget{
size_t m_width;
size_t m_height;
reader_writer_lock lock;
public:
LockedWidget (size_t w, size_t h):m_width(w),m_height(h){};
size_t GetWidth(){
auto lockGuard = reader_writer::scoped_lock_read(lock);
return m_width;
}
size_t GetHeight(){
auto lockGuard = reader_writer::scoped_lock_read(lock);
return m_height;
}
void SetWidth(size_t width){
auto lockGuard = reader_writer::scoped_lock(lock);
m_width = width;
}
void SetHeight(size_t height){
auto lockGuard = reader_writer::scoped_lock(lock)
m_height = height;
}
};

什麼我已經完成這裡是將一個 read_writer_lock 新增為成員變數然後裝飾與讀取器或寫入器鎖定版本的所有適當的方法。 我也使用 scoped_lock 物件,以確保不保留 [鎖定保留例外狀況中。 取得的所有方法現在都取得讀取器鎖定中,並設定方法都取得寫入鎖定。 技術上,這種方法看起來像它是正確的但設計是實際上不正確而易損壞整體因為其介面結合時, 並不具備執行緒安全。 特別是,如果有下列的程式碼我可能有損毀的狀態:

Thread1{
                SharedWidget.GetWidth();
                SharedWidget.GetHeight();
}
Thread2{
                SharedWidget.SetWidth();
                SharedWidget.SetHeight();
}

Thread1 因為 Thread1 和 Thread2 呼叫可以交錯,可以取得讀取的鎖定的 GetWidth,然後 GetHeight 稱為之前先 SetWidth 和 SetHeight 可以同時執行。 這樣除了保護資料,您就需要確定該資料介面也是正確的 ;這是其中一個最陰險競爭情形的類型,因為程式碼看起來正確而且很難追蹤錯誤。 天真無邪解決方案過此種狀況通常牽涉到引入物件本身的鎖定方法 (或更糟的是,鎖定儲存其他地方取得存取該 Widget 時,請記得要開發人員的。 有時會使用這兩種方法。

一個簡單的方法是為了確保介面可以被交錯安全地不公開此能夠清除之間交錯呼叫物件的狀態。 您可能會決定要發展您的介面中所示的圖 3 提供 GetDimensions 和 UpdateDimensions 方法。 此介面目前已比較不可能會造成意外的行為,因為方法不允許公開 interleavings 不安全的。

圖 3 A 的 「 介面以 GetDimensions 和 UpdateDimensions 方法的版本

struct WidgetDimensions
{
size_t width;
size_t height;
};
class LockedWidgetEx{
WidgetDimensions m_dimensions;
reader_writer_lock lock;
public:
LockedWidgetEx(size_t w, size_t h):
m_dimensions.width(w),m_dimensions.height(h){};
WidgetDimensions GetDimensions(){
auto lockGuard = reader_writer::scoped_lock_read(lock);
return m_dimensions;
}
void UpdateDimensions(size_t width, size_t height){
auto lockGuard = reader_writer::scoped_lock(lock);
m_dimensions.width = width;
m_dimensions.height = height;
}
};

管理共用的狀態訊息區塊

現在讓我們會看一下代理程式庫如何協助進行管理共用狀態更容易和更健全的程式碼。 稱為 「 管理共用的變數是儲存為單一的可更新值的 overwrite_buffer < T >,和接收的傳回一份最新值時非常有用的類別索引鍵從代理程式庫 ;single_assignment < T > 的存放區和傳回一份單一值時接收呼叫,但像一個的常數可以指派一次 ;unbounded_buffer < T >,儲存數目沒有限制 (允許的記憶體) 的項目和,像 FIFO 佇列,dequeues 最舊的項目,並當接收呼叫。

我將開始使用的 overwrite_buffer < T >。 Widget 類別中, 我會先以 overwrite_buffer < WidgetDimensions >,取代 m_dimensions 成員變數,而然後我會在此方法從移除明確的鎖定以及取代適當的傳送和接收呼叫。 我仍需要擔心正在安全,我們介面,但不會再有要記得鎖定資料。 以下是外觀將程式碼中。 它是比鎖定的版本和為序列的版本相同的行數的實際稍微較少行程式碼:

class AgentsWidget{
    overwrite_buffer<WidgetDimensions> m_dimensionBuf;
public:
    AgentsWidget(size_t w, size_t h){
        send(&m_dimensionBuf,WidgetDimensions(w,h));
    };
    WidgetDimensions GetDimensions(){
        return receive(&m_dimensionBuf);
    }
    void UpdateDimensions(size_t width, size_t height){
        send(&m_dimensionBuf,WidgetDimensions(w,h));
    }
};

還有細微語意差異這裡 reader_writer 鎖定實作。 overwrite_buffer 允許 UpdateDimensions 維度呼叫時進行呼叫。 這可讓幾乎沒有封鎖這些的呼叫期間,但 GetDimensions 呼叫可能會稍微過期。 值得指出問題位於已鎖定的版本,就也因為一旦您取得維度,它們有可能已過期。 這裡,我所做的就是移除封鎖的呼叫。

也可以用於 Widget 類別的 unbounded_buffer。 想像一下微妙的語意差異,只是描述是非常重要。 就例如如果您有您想要確保物件的一個執行個體一次只能由一個執行緒存取,您可以使用 unbounded_buffer 作為管理存取該物件的物件擁有者。 若要這套 Widget 類別您可以決定要移除 m_dimensions 並取代它與 unbounded_buffer < WidgetDimension >並使用透過 UpdateDimensions GetDimensions 來呼叫這個緩衝區。 這裡的挑戰是要確保沒有人可以取得值從我們的 Widget 時正在更新。 這是藉由使 GetDimension 呼叫會封鎖等候更新發生清空 unbounded_buffer 來達成。 您可以參閱 [圖 4]。 GetDimensions 和 UpdateDimensions 區塊,等待獨占存取該維度的變數。

圖 4 清空 Unbounded_Buffer

class AgentsWidget2{
unbounded_buffer<WidgetDimensions> m_dimensionBuf;
public:
AgentsWidget2(size_t w, size_t h){
send(&m_dimensionBuf,WidgetDimensions(w,h));
};
WidgetDimensions GetDimensions(){
//get the current value
WidgetDimensions value = receive(&m_dimensionBuf);
//and put a copy of it right back in the unbounded_buffer
send(&m_dimensionBuf,value);
//return a copy of the value
return WidgetDimensions(value);
}
void UpdateDimensions (size_t width, size_t height){
WidgetDimensions oldValue = receive(&m_dimensionBuf);
send(&m_dimensionBuf,WidgetDimensions(width,height));
}
};

它是真正 Coordinating 資料的存取權

我想要壓力有關我們 Widget 類別一件事:確保方法和可以同時存取的資料使用 「 安全 」一起是很重要。 通常,這也能得到協調的狀態而鎖定的方法或物件的存取。 從一個純 「 線的程式碼 」觀點來看,您不會看到一個大贏透過鎖定範例,並在就特別第二個範例甚至可能有點更多程式碼。 什麼取得,但是,是安全的設計,而且一點點的想法與可以經常修改序列介面,所以的內部物件的狀態並不會 」 損壞]。在 Widget 的範例使用訊息區塊,我做但我能夠保護方式則會比較安全的狀態。 新增的方法或 Widget 類別的功能在未來是比較不可能摧毀我們設定了內部的同步處理。 與成員鎖定,很直接忘了在類別上加入一個方法時,鎖定鎖定很簡單。 但是將作業移至訊息傳遞模式,並使用訊息區塊 (例如資料和類別,通常可以保留其自然的方式覆寫緩衝區同步處理。

三個:使用 Combinable 的執行緒本機 Accumulations 和初始化

第二個案例中我們保護存取的物件與鎖定] 或 [訊息區塊,很適用於不常存取的加重加權物件。 如果讀取該範例時,您認為有可能是效能問題如果同步的 Widget 已緊密及平行迴圈中使用,您就可能權限。 這是因為保護共用的狀態可能有問題,完全一般用途的演算法和真正的共用狀態的物件,不幸的是沒有很多的選項以外的其他協調存取,或引入鎖定。 但您一定可以找到您能夠重整程式碼或放鬆上共用的狀態相依性的演算法,而一次,您已經完成這幾個特定的但一般模式中的物件呼叫可結合 < T >平行模式程式庫中確實有助於出。

可組合 < T >是並行容器提供支援三種廣泛的使用情況:按住執行緒區域變數或執行執行緒本機初始化、 執行緒本機變數上執行 (例如加總、 最小及混合) 關聯的二進位作業和合併它們,及瀏覽使用作業 (例如一起 splicing 清單) 的每一個執行緒本機複本。 這的一節,我會說明每種情況下,並提供如何使用它們的範例。

按住執行緒區域變數,或執行執行緒本機初始化

第一個使用案例中我提到的可組合 < T >來存放執行緒區域變數。 是相當常見儲存執行緒區域複本的全域狀態。 就例如中,有色彩條永無止盡的射線 tracers 應用程式要我們的範例套件 (code.msdn.microsoft.com/concrtextras) 中或使用.NET 4.0 (code.msdn.microsoft.com/ParExtSamples) 有平行開發的範例中由視覺化平行處理的執行緒標示每個資料列的選項。 在原生版本的示範,做法是使用可組合的物件擁有執行緒區域色彩。

您可以藉由使用執行緒區域儲存區 (TLS) 的就說保留執行緒區域變數,但是有一些缺點,最值得注意的是存留期 (Lifetime) 管理可視性,和這些手動在手動執行。 若要將 TLS 您必須先配置 TlsAlloc 的索引、 配置您的物件,然後將您物件的指標儲存在索引中用 TlsSetValue。 然後時您的執行緒正在結束,, 您必須確保您的物件解除配置。 (TlsFree 稱為自動)。執行這項操作一次或兩次每執行緒,並確保沒有任何遺漏 (Memory Leak) 的最早結束因為或例外狀況不該挑戰,但如果您的應用程式需要數十或數百個這些項目,不同的方法可能是更好。

可組合 < T >可以用來儲存執行緒區域值,就也,但在個別物件的存留期會繫結至的可組合 < T > 存留期被自動項目,] 和 [大部分的初始化。 若要存取執行緒區域值,只是先呼叫 [combinable::local] 方法會傳回本機物件的參照。 以下是使用 task_group,範例,但可以使用 Win32 執行緒也執行:

combinable<int> values;

auto task = [&](){
                values.local() = GetCurrentThreadId();
                printf("hello from thread: %d\n",values.local());
};

task_group tasks;

tasks.run(task);

//run a copy of the task on the main thread
task();

tasks.wait();

我提到該執行緒本機初始化也可以與可結合來達成。 如果,就例如要初始化對每個執行緒在其使用程式庫呼叫,您可以建立一個類別,它的建構函式中執行的初始化。 然後,在第一次每個執行緒使用,在程式庫呼叫將會進行,但會略過在後續使用。 下面是一個範例:

class ThreadInitializationClass
{
public:
      ThreadInitializationClass(){
            ThreadInitializationRoutine();
      };
};

...
//a combinable object will initialize these
combinable<ThreadInitializationClass> libraryInitializationToken;
            
...
//initialize the library if it hasn't been already on this thread
ThreadInitializationClass& threadInit = libraryInitalizationToken.local();

在平行迴圈中執行縮減

另一個可組合的物件的主要案例是執行執行緒區域縮減或執行緒區域 accumulations。 特別是,您可以避免競爭情形平行處理迴圈時或在與可組合的遞迴平行 traversals 特定型別。 以下是一個非常天真無邪範例不是用來顯示 speed-ups。 下列程式碼顯示簡單的迴圈,看起來像它可以平行處理與 parallel_for_each,以外的加總變數存取:

 

int sum = 0;
for (vector<int>::iterator it = myVec.begin(); it != myVec.end(); ++it) {
    int element = *it;
    SomeFineGrainComputation(element);
    sum += element;
}

現在,而不是將鎖定放在我們摧毀我們的 speed-ups,任何機會的 parallel_for_each 中,我們可以使用 [可結合的物件] 來計算執行緒區域:

combinable<int> localSums;
 
parallel_for_each(myVec.begin(),  myVec.end(), [&localSums] (int element) {
   SomeFineGrainComputation(element);
   localSums.local() += element;
});

我們現在已成功地避免競爭,但我們有執行緒區域儲存於所 localSums] 物件中的加總的集合,我們仍然需要擷取最後的值]。 我們可以使用 [合併] 方法會像下面是二進位 functor 執行:

int sum = localSums.combine(std::plus<int>);

第三個使用案例的可組合 < T > 其中牽涉到使用 combine_each 的方法是當您需要瀏覽每個執行緒本機複本,並執行的某些作業 (像是清除或錯誤檢查])。 您可組合的物件是可組合 < 清單 < T > >,且在執行緒裡您正在建置 std::lists 或 std::sets 時,更有趣的範例。 std::lists 的情況下它們可以輕易地被 spliced 搭配 list::splice ;與 std::sets,它們可以插入與 set::insert。

四個:將現有的背景執行緒轉換成代理程式或工作

假設您已經有您的應用程式中的背景工作執行緒。 有一些很好的原因原因您可能想要轉換成工作的背景執行緒,從 [PPL 或的代理程式,而且這樣做是相當直接的方法。 執行這項操作的主要優點包括下列:

Composability 和效能。 如果您的背景工作執行緒會計算耗用大量資源,而且您會考慮使用 PPL 或代理程式庫中的其他執行緒將您的背景執行緒轉換為背景工作的工作可讓它相互合作,進行其他工作在執行階段中,以避免 oversubscription 系統上。

取消和例外處理。 如果您想要能夠輕易地取消在執行緒上的工作,或具有 well-described 處理例外狀況的機制,一個 task_group 會有這些內建的功能。

控制流程和狀態管理。 如果您要管理您的執行緒 (開始或完成的範例) 的狀態,或有其狀態為有效 inseparable 從背景工作執行緒的物件,實作代理程式可能會很有用。

Task_group 提供取消] 和 [例外狀況處理

在第一個的案例中,我們探索如何排程一個 task_group 使用:基本上封裝 (使用一個 Lambda、 一個 std::bind 或自訂函式物件) 是 functor 到您的工作和排程它與 task_group::run 方法。 我沒有說明是,在就其實相關的例外狀況處理語意與取消通知。

圖 5 MyAgentClass 的實作

class MyAgentClass : public agent{
public:
MyAgentClass (){
}
AgentsWidget widget;
void run(){
//run is started asynchronously when agent::start is called
//...
//set status to complete
agent::done();
}
};

第一次,我將說明簡單的語意。 如果您的程式碼會呼叫 task_group::cancel,或是工作會擲回無法攔截例外狀況,取消為作用中的 task_group。 時取消作用中,不會啟動尚未在該 task_group 已開始的任務允許輕易快速地取消上一個 task_group 的排程的工時。 取消並不會岔斷任務的執行或封鎖,所以正在執行的工作可以查詢取消狀態,與 task_group::is_canceling 方法或 Helper 函式

is_current_task_group_canceling。 以下是一個簡短的範例:

task_group tasks;   
tasks.run([](){
    ...
    if(is_current_task_group_canceling())
    {
        //cleanup then return
        ...
        return;
    }
});
tasks.cancel();
tasks.wait();

例外處理會影響取消,因為無法攔截例外狀況中是 task_group 觸發程序上的 task_group 取消。 如果有一個無法攔截的例外狀況,task_group 其實是使用 std::exception_ptr 封裝安裝在它在擲回的執行緒上的例外狀況。 重新擲稍後,呼叫 task_group::wait 時, 的例外狀況是回呼叫等候的執行緒上。

實作的非同步代理程式

代理程式庫提供使用一個 task_group 的替代方法:取代代理程式的基底類別中的執行緒。 如果您的執行緒有很多執行緒特定狀態的物件,代理程式可能是最適分析藍本。 抽象的代理程式類別是動作項目 ; 模式的實作預期的使用方式是實作您自己的類別衍生自代理程式,然後封裝到該代理程式的 [動作項目 (或執行緒) 可能會有任何狀態。 如果欄位為了可公開存取,本指南是將它們公開為訊息區塊或來源和目標,並使用訊息傳遞至與代理程式通訊。

實作的代理程式,將需要從代理程式的基底類別衍生出類別並再覆寫虛擬方法執行。 然後可以藉由呼叫 agent::start 產生工作,十分類似執行緒的執行的方法的中啟動代理程式。 優點是執行緒本機狀態現在可以儲存在該類別。 這可讓更容易的同步處理執行緒,之間的狀態的特別是如果狀態儲存訊息區中。 圖 5 顯示具有公開的成員變數的型別 AgentsWidget 實作的範例。

請注意,我已設定代理程式的狀態為作業時執行的方法正在結束。 這可讓不只會啟動,但也等候上代理程式。 此外,agent::status 呼叫可以被查詢代理程式的目前狀態。 啟動並等候代理程式類別是簡單如下列程式碼所示:

 

MyAgentClass MyAgent;

//start the agent
MyAgent.start();

//do something else
...

//wait for the agent to finish
MyAgent.wait(&MyAgent);

額外的項目:排序與 parallel_sort 平行

最後,我想建議的平行處理,不是從 [PPL 或代理程式庫,但從我們的範例套件位於 code.msdn.microsoft.com/concrtextras 這次的另一個可能容易點。 平行的快速排序是我們使用,說明如何在平行處理工作,遞迴分界,並征服演算法的範例,而且範例封包包含平行的快速排序的實作。 平行排序可以顯示 speed-ups 是否您排序大量有點昂貴,使用字串比較作業所在的項目。 它可能不會顯示小的數字的項目或 speed-ups 排序像整數和雙精度浮點數的內建型別時。 以下是如何使用它的一個範例:

//from the sample pack
#include "parallel_algorithms.h"
int main()

using namespace concurrency_extras;
{
                vector<string> strings;
 
                //populate the strings
                ...
                parallel_sort(strings.begin(),strings.end());
}

總結

希望此資料行可以協助您展開 horizons 的如何在 Visual Studio 2010 平行程式庫會套用到您的專案,超過只使用 parallel_for] 或 [工作來加速運算密集迴圈。 在我們在 MSDN 上的文件中找到許多其他指示範例 (msdn.microsoft.com/library/dd504870(VS.100).aspx) 和協助說明平行程式庫,以及如何使用我們範例套件 (code.msdn.microsoft.com/concrtextras)。 我建議您在簽出。

Rick Molloy 是在平行運算平台小組在 Microsoft 程式管理員。