2016-09-08 31 views
-1

我有一個VS2015解決方案,包含非託管C++代碼(用於執行一些CPU密集型仿真計算),圍繞此代碼的C++/cli包裝以及通過DLL調用C++/cli包裝的ac#項目。以下示例是完整代碼的簡化版本,對於代碼數量提前感到抱歉,但需要全面瞭解正在進行的操作。避免std :: deque迭代器在非託管C++,C++/cli和c#之間的不可忽略錯誤代碼

不受管理的C++代碼

class diffusion_limited_aggregate { 
public: 
    diffusion_limited_aggregate() 
     : aggregate_map(), attractor_set(), batch_queue() {} 
    std::size_t size() const noexcept { return aggregate_map.size(); } 
    std::queue<std::pair<int,int>>& batch_queue_handle() noexcept { return batch_queue; } 
    void generate(std::size_t n) { 
     initialise_attractor_structure(); // set up initial attractor seed points 
     std::size_t count = 0U; 
     std::pair<int,int> current = std::make_pair(0,0); 
     std::pair<int,int> prev = current; 
     bool has_next_spawned = false; 
     while (size() < n) { 
      if (!has_next_spawned) { 
       // => call function to spawn particle setting current 
       has_next_spawned = true; 
      } 
      prev = current; 
      // => call function to update random walking particle position 
      // => call function to check for lattice boundary collision 
      if (aggregate_collision(current, prev, count)) has_next_spawned = false; 
     } 
    } 
    void initialise_attractor_structure() { 
     attractor_set.clear(); 
     attractor_set.insert(std::make_pair(0,0)); 
    } 
    void push_particle(const std::pair<int,int>& p, std::size_t count) { 
     aggregate_map.insert(std::make_pair(p, count)); 
     batch_queue.push(p); 
    } 
    bool aggregate_collision(const std::pair<int,int>& current, 
     const std::pair<int,int>& prev, std::size_t& count) { 
     if (aggregate_map.find(current) != aggregate_map.end() 
      || attractor_set.find(current) != attractor_set.end()) { 
      push_particle(previous, ++count); 
      return true; 
     } 
     return false; 
    } 
private: 
    std::unordered_map<std::pair<int,int>, 
     std::size_t, 
     utl::tuple_hash> aggregate_map; 
    std::unordered_set<std::pair<int,int>, utl::tuple_hash> attractor_set; 
    std::queue<std::pair<int,int>> batch_queue; // holds buffer of aggregate points 
}; 

哪裏utl::tuple_hashstd::pair散列函數對象,並且更一般地,std::tuple情況下,定義爲:

namespace utl { 
    template<class Tuple, std::size_t N> 
    struct tuple_hash_t { 
     static std::size_t tuple_hash_compute(const Tuple& t) { 
      using type = typename std::tuple_element<N-1, Tuple>::type; 
      return tuple_hash_t<Tuple,N-1>::tuple_hash_compute(t) 
       + std::hash<type>()(std::get<N-1>(t)); 
     } 
    }; 
    // base 
    template<class Tuple> 
    struct tuple_hash_t<Tuple, 1> { 
     static std::size_t tuple_hash_compute(const Tuple& t) { 
      using type = typename std::tuple_element<0,Tuple>::type; 
      return 51U + std::hash<type>()(std::get<0>(t))*51U; 
     } 
    }; 
    struct tuple_hash { 
     template<class... Args> 
     std::size_t operator()(const std::tuple<Args...>& t) const { 
      return tuple_hash_t<std::tuple<Args...>,sizeof...(Args)>::tuple_hash_compute(t); 
     } 
     template<class Ty1, class Ty2> 
     std::size_t operator()(const std::pair<Ty1, Ty2>& p) const { 
      return tuple_hash_t<std::pair<Ty1,Ty2>,2>::tuple_hash_compute(p); 
     } 
    }; 
} 

託管C++/CLI包裝器

以下是圍繞類diffusion_limited_aggregate的C++/cli中的一個包裝,這種情況下的重要方法是ProcessBatchQueue。此方法是std::deque iterator not dereferencable error必須發生的地方,因爲它是訪問和彈出batch_queue內容的唯一位置。

public ref class ManagedDLA2DContainer { 
private: 
    diffusion_limited_aggregate* native_dla_2d_ptr; 
    System::Object^ lock_obj = gcnew System::Object(); 
public: 
    ManagedDLA2DContainer() : native_dla_2d_ptr(new diffusion_limited_aggregate()) {} 
    ~ManagedDLA2DContainer() { delete native_dla_2d_ptr; } 
    std::size_t Size() { return native_dla_2d_ptr->size(); } 
    void Generate(std::size_t n) { native_dla_2d_ptr->generate(n); } 
    System::Collections::Concurrent::BlockingCollection< 
     System::Collections::Generic::KeyValuePair<int,int> 
    >^ ProcessBatchQueue() { 
     // store particles in blocking queue configuration 
     System::Collections::Concurrent::BlockingCollection< 
      System::Collections::Generic::KeyValuePair<int,int>>^ blocking_queue = 
      gcnew System::Collections::Concurrent::BlockingCollection< 
       System::Collections::Generic::KeyValuePair<int,int> 
      >(); 
     System::Threading::Monitor::Enter(lock_obj); // define critical section start 
     try { 
      // get ref to batch_queue 
      std::queue<std::pair<int,int>>& bq_ref = native_dla_2d_ptr->batch_queue_handle(); 
      // loop over bq transferring particles to blocking_queue 
      while (!bq_ref.empty()) { 
       auto front = std::move(bq_ref.front()); 
       blocking_queue->Add(System::Collections::Generic::KeyValuePair<int,int>(front.first,front.second)); 
       bq_ref.pop(); 
      } 
     } 
     finally { System::Threading::Monitor::Exit(lock_obj); } 
     return blocking_queue; 
    } 
} 

C#代碼

最後,我有一個使用ManagedDLA2DContainer以產生聚集體和一個接口上顯示它們下面的C#代碼。

public partial class MainWindow : Window { 
    private static readonly System.object locker = new object(); 
    private readonly ManagedDLA2DContainer dla_2d; 
    public MainWindow() { 
     InitializeComponent(); 
     dla_2d = new ManagedDLA2DContainer(); 
    } 
    private void GenerateAggregate(uint n) { 
     // start asynchronous task to perform aggregate simulation computations 
     Task.Run(() => CallNativeCppAggregateGenerators(n)); 
     System.Threading.Thread.Sleep(5); 
     // start asynchronous task to perform rendering 
     Task.Run(() => AggregateUpdateListener(n)); 
    } 
    private void CallNativeCppAggregateGenerators(uint n) { 
     dla_2d.Generate(n); 
    } 
    private void AggregateUpdateListener(uint n) { 
     const double interval = 10.0; 
     Timer timer = new Timer(interval); 
     timer.Elapsed += Update2DAggregateOnTimedEvent; 
     timer.AutoReset = true; 
     timer.Enabled = true; 
    } 
    private void Update2DAggregateOnTimedEvent(object source, ElapsedEventArgs e) { 
     lock(locker) { 
      BlockingCollection<KeyValuePair<int,int>> bq = dla_2d.ProcessBatchQueue(); 
      while(bq.Count != 0) { 
       KeyValuePair<int,int> p = bq.Take(); 
       Point3D pos = new Point3D(p.Key, p.Value, 0.0); 
       // => do stuff with pos, sending to another class method for rendering 
       // using Dispatcher.Invoke(() => { ... }); to render in GUI 
      } 
     } 
    } 
} 

方法GenerateAggregate僅稱爲每個聚集體執行一次,它被通過一個按鈕的處理程序方法稱爲我有它調用一個GenerateAggreateOnGenerateButtonClicked事件處理函數的界面上形成Generate方法。代碼中的其他任何地方都不會調用CallNativeCppAggregateGeneratorsAggregateUpdateListener


的問題

如託管的包裝部分,執行該代碼時,我偶爾會在運行時斷言錯誤提到,

std::deque迭代器不dereferencable。

這往往會在首次執行時發生,但它也會出現在正在進行的總計生成過程中,因此生成聚合的啓動代碼很可能不是此處的罪魁禍首。

我該如何着手解決這個問題?希望這是在我的關鍵部分代碼或類似的一些邏輯錯誤的簡單情況下,但我還沒有找到確切的問題。

正如在評論中指出,該問題可能是元素正在不斷增加batch_queue而C#線程調用ProcessBatchQueue在消費隊列中的元素,從而可能無效batch_queue的迭代器。是否有典型的生產者 - 消費者設計模式可以應用於這種用例?

編輯:這將是很好,如果downvoter可以給自己的理由,這樣我可以改善這個問題。

+0

在嘗試使用迭代器之前,您可以添加一個檢查以查看容器是否爲空?這可以幫助縮小問題的來源。 – NathanOliver

+0

@NathanOliver我想我可以,但我假設,出現此錯誤是由於張女士不知何故'batch_queue'變空,而裏面的',而(!bq_ref.empty())'ProcessBatchQueue'的'循環,只要因爲它不是空的,那麼我認爲(也許我錯了)訪問'front'和'pop'隊列將是明確的操作。 – ArchbishopOfBanterbury

+1

您沒有顯示從何處調用GenerateAggregate。 CallNativeCppAggregateGenerators是否從其他地方撥打電話?如果在處理現有隊列時向隊列添加新元素,則在處理過程中使用的迭代器將失效。 – 1201ProgramAlarm

回答

0

我來到了這個問題,這將在下文詳述的解決方案。正如問題中提出的那樣,問題是在處理batch_queue時,由於在元素生成過程中不斷將元素推送到隊列中,它的「迭代器偶爾會失效。

該解決方案使用略微更多的內存比基於以前batch_queue實現,但它是安全的,只要迭代器的有效性而言。

class diffusion_limited_aggregate { 
public: 
//... 
    const std::vector<std::pair<int,int>>& aggregate_buffer() const noexcept { return buffer; } 
private: 
//... 
    std::vector<std::pair<int,int>> buffer; 
}; 

然後ManagedDLA2DContainer::ProcessBatchQueueManagedDLA2DContainer::ConsumeBuffer替換讀取到一個顯着的索引並且推動最近一批骨料顆粒到交流#List<KeyValuePair<int,int>>:我在本地C++代碼std::vector<std::pair<int,int>>緩衝器聚集體顆粒的取代batch_queue

System::Collections::Generic::List<System::Collections::Generic::KeyValuePair<int, int>>^ ConsumeBuffer(std::size_t marked_index) { 
     System::Collections::Generic::List<System::Collections::Generic::KeyValuePair<int, int>>^ buffer = 
      gcnew System::Collections::Generic::List<System::Collections::Generic::KeyValuePair<int, int>>(); 
     if (native_dla_2d_ptr->aggregate_buffer().empty()) return buffer; 
     System::Threading::Monitor::Enter(lock_obj); // define critical section start 
     try { // execute critical section 
      // read from last marked buffer index up to size of buffer and write these data to batch list 
      for (int i = marked_index; i < native_dla_2d_ptr->aggregate_buffer().size(); ++i) { 
       buffer->Add(System::Collections::Generic::KeyValuePair<int, int>(
        native_dla_2d_ptr->aggregate_buffer()[i].first, 
        native_dla_2d_ptr->aggregate_buffer()[i].second 
        ) 
       ); 
      } 
     } 
     finally { System::Threading::Monitor::Exit(lock_obj); } // exit critical section by releasing exclusive lock 
     return buffer; 
} 

最後在c#MainWindow::Update2DAggregateOnTimedEvent方法的代碼被改變以反映在C++/CLI代碼這些變化:

private void Update2DAggregateOnTimedEvent(object source, ElapsedEventArgs e, uint n) { 
    lock (locker) { 
     List<KeyValuePair<int,int>> buffer = dla_2d.ConsumeBuffer(
      (current_particles == 0) ? 0 : current_particles-1); // fetch batch list 
     foreach (var p in buffer) { 
      // => add p co-ords to GUI manager... 
      ++current_particles; 
      // => render aggregate... 
     } 
    } 
}