簡體   English   中英

避免非托管C ++,C ++ / CLI和C#代碼之間的std :: deque迭代器不可解除錯誤

[英]Avoiding std::deque iterator not dereferencable error between unmanaged c++, c++/cli and c# code

我有一個VS2015解決方案,其中包括不受管理的c ++代碼(以執行一些CPU密集型模擬計算),圍繞此代碼的c ++ / cli包裝器以及ac#項目,該項目通過DLL調用c ++ / cli包裝器。 下面的示例是完整代碼的簡化版本,對於預先提供的代碼數量感到抱歉,但是對於所發生的情況的完整了解是必需的。

非托管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包裝器

以下是在圍繞類C ++ / CLI的包裝diffusion_limited_aggregate ,在這種情況下,重要的方法是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#代碼

最后,我有以下c#代碼,該代碼使用ManagedDLA2DContainer生成聚合並將其顯示在界面上。

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只叫每個聚集執行一次,它是通過一個按鈕處理程序方法被稱為我有一個Generate帶有該接口上的方法OnGenerateButtonClicked它調用的事件處理函數GenerateAggreate 在代碼中的其他任何地方, CallNativeCppAggregateGeneratorsAggregateUpdateListener均未調用。


問題

如托管包裝部分所述,執行此代碼時,我偶爾會收到運行時斷言錯誤,

std::deque迭代器不可取消。

這通常在首次執行時發生,但也確實在正在進行的聚合生成過程的中間發生,因此,生成聚合的啟動代碼在這里可能不是罪魁禍首。

我該如何解決這個問題? 希望這是我的關鍵部分代碼或類似代碼中出現邏輯錯誤的簡單情況,但是我還無法查明確切的問題。

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

編輯:如果下降投票者可以給出他們的理由,這樣我可以改善這個問題,那就太好了。

我為這個問題找到了解決方案,下面將詳細介紹。 如問題中所建議的那樣,問題在於,在處理batch_queue時,由於在聚合生成過程中將元素連續推入隊列,其迭代器有時會失效。

該解決方案比以前的基於batch_queue的實現使用更多的內存,但是就迭代器的有效性而言,它是安全的。 我用本地c ++代碼中的聚合粒子的std::vector<std::pair<int,int>>緩沖區替換了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::ProcessBatchQueue替換為ManagedDLA2DContainer::ConsumeBuffer ,該ManagedDLA2DContainer::ConsumeBuffer讀取標記的索引並將最新一批的聚集粒子推入ac# List<KeyValuePair<int,int>>

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...
        }
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM