2016-10-04 99 views
2

我正在C++框架中工作,主要是在C++ 11之前編寫的,它允許我們將事件從一個線程引發到另一個線程(假設接收線程正在運行事件隊列,所以它主要用於從輔助線程向主UI線程中引發事件)。在不同線程中調用std :: function

目前,完成這一任務的代碼非常冗長 - 它需要我們定義兩個類:

  1. 包含我們想要傳遞的任何信息事件類。
  2. 在接收線程中處理事件的偵聽器類。

我們最近轉移到了C++ 11/14,並且一直致力於更新我們的許多代碼以使用智能指針,標準容器和lambda表達式。我想寫一個通用類,允許我發送一個lambda在不同的線程中運行。喜歡的東西:

mBoundary = make_unique<ThreadBoundary>([](int value) { doSomething(value); }); 
mBoundary->callInMainThread(47); 

ThreadBoundary boundary2([](std::string value) { displayString(value); }); 
boundary2.callInMainThreadWait("Show this string to the user"); 

作爲一個初步的嘗試,我現在有一個拉姆達不帶任何參數,建立在目前的框架功能之上這方面的工作(省略錯誤檢查,清理等) :

class ThreadBoundary 
{ 
public: 
    ThreadBoundary(std::function<void()> function):mFunction(function) 
    { 
     mListener = make_shared<ThreadBoundaryListener>(); 
     cApplication::addThreadBoundaryListener(mListener); 
    } 

    void callInMainThread() 
    { 
     cApplication::fireEventInMainThread(new ThreadBoundaryEvent(mFunction)); 
    } 

    class ThreadBoundaryEvent:public FrameworkThreadBoundaryEvent 
    { 
    public: 
     ThreadBoundaryEvent(std::function<void()> function) 
     { 
      mFunction = function; 
     } 

     void call() { mFunction(); } 

    private: 
     std::function<void()> mFunction; 
    }; 

    class ThreadBoundaryListener:public FrameworkThreadBoundaryListener 
    { 
    public: 
     ThreadBoundaryListener() {} 

     void handleEvent(const FrameworkThreadBoundaryEvent* event) 
     { 
      dynamic_cast<const ThreadBoundaryEvent*>(event)->call(); 
     } 
    }; 

private: 
    shared_ptr<ThreadBoundaryListener> mListener; 
    std::function<void()> mFunction; 
}; 

這讓我不過火「一次性」事件給主線程,而不能沿參數發送給它的功能是相當有限。

我想讓這個類使用可變參數模板,以便我可以傳遞任何東西給callInMainThread函數。但是,我一直無法弄清楚如何將參數包存儲在事件中,並將它傳遞給call()中的函數。所以我的問題是:

  1. 是否有可能存儲參數包,然後傳遞給std :: function?
  2. 有沒有更好的方法來做到這一點在c + + 11/14,不需要大量的重新設計?我們的框架目前正在封裝OS功能來實現這一點(即在Windows上它使用SendMessage vs OS X中的performSelectorOnMainThread)。

回答

2

不是將接口構造從調用中分離出來,而是使用lambdas的功能將參數捕獲到函數對象本身(閉包)中,以便每次調用都是相同的。沒有基本的線程通信對象封裝一個函數類型。相反,在你打電話時傳遞整個功能。去這個接口:

threadBoundary.callInMainThread([value](){doSomething(value);}); 

如果傳遞一切都是一個函數對象不帶參數,可以很容易地通過,並易於調用。捕捉任何你需要傳遞的信息。

對於一些接口和實現的想法,請看boost::asio如何實現它的io_service,它允許你像這樣發佈函數調用到其他線程。

+0

出於某種原因,我認爲有些情況下,這將無法正常工作,但目前我無法做出任何決定。所以我想我會用你的解決方案,謝謝! – Runt8

0

空線函數調用更容易跨線程邊界發送。捕獲您想要傳遞到lambda捕獲列表的參數很容易。

我願意接受。

但是,您可以編寫代碼來非常容易地將非空值函數調用轉換爲空值函數調用。如果你想「凍結」沒有參數的函數調用,簽名也必須被凍結。

tempate<class Sig> 
struct freeze_function; 
tempate<class R, class...Args> 
struct freeze_function<R(Args...)> { 
    std::function< R(Args...) > to_freeze; 
    freeze_function() = default; 
    freeze_function(freeze_function&&) = default; 
    freeze_function(freeze_function const&) = default; 
    freeze_function& operator=(freeze_function&&) = default; 
    freeze_function& operator=(freeze_function const&) = default; 
    freeze_function(std::function< void(Args...) > f):to_freeze(std::move(f)) {} 

    std::function<R()> operator()(Args... args) const { 
    return [=, frozen=this->to_freeze]->R{ 
     return frozen(args...); 
    }; 
    } 
}; 

freeze_function<void(int)>可以從拉姆達服用int來構造。然後您可以將它傳遞給int,並返回一個std::function<void()>與該int的界限。

但我認爲這不是必需的。

(擺脫freeze_function最佳效率所需的額外的工作;它不必要的副本args...我折騰args...成一個元組則拉姆達內解開他們,也許使用std::apply從C++ 17的等價物。)

這可以通過添加一個簽名(使它成爲一個模板)被要麼烤到ThreadBoundary,或可以被用來存儲你的未實現的函數調用的地方,並凍結他們之前立即使所得std::function<void()>ThreadBoundary