Я только вхожу в параллельное программирование. Скорее всего, моя проблема очень распространена, но, поскольку я не могу найти для нее подходящего названия, я не могу ее погуглить.
У меня есть приложение C ++ UWP, в котором я пытаюсь применить шаблон MVVM, но я предполагаю, что шаблон или даже UWP не имеет значения.
Во-первых, у меня есть сервисный интерфейс, который представляет операцию:
struct IService
{
virtual task<int> Operation() = 0;
};
Конечно, я предоставляю конкретную реализацию, но она не актуальна для этого обсуждения. Операция потенциально длительная: она отправляет HTTP-запрос.
Затем у меня есть класс, который использует службу (опять же, нерелевантные детали опущены):
class ViewModel
{
unique_ptr<IService> service;
public:
task<void> Refresh();
};
Я использую сопрограммы:
task<void> ViewModel::Refresh()
{
auto result = co_await service->Operation();
// use result to update UI
}
Функция Refresh вызывается по таймеру каждую минуту или в ответ на запрос пользователя. То, что я хочу: если операция обновления уже выполняется, когда запускается или запрашивается новая, то отмените вторую и просто дождитесь завершения первой (или тайм-аут). Другими словами, я не хочу ставить все вызовы в очередь на обновление — если вызов уже выполняется, я предпочитаю пропустить вызов до следующего таймера.
Моя попытка (вероятно, очень наивная) была:
mutex refresh;
task<void> ViewModel::Refresh()
{
unique_lock<mutex> lock(refresh, try_to_lock);
if (!lock)
{
// lock.release(); commented out as harmless but useless => irrelevant
co_return;
}
auto result = co_await service->Operation();
// use result to update UI
}
Редактировать после исходного поста: я закомментировал строку во фрагменте кода выше, так как это не имеет значения. Вопрос все тот же.
Но, конечно, утверждение неверно: unlock of unowned mutex
, Я думаю, что проблема заключается в unlock
из mutex
от unique_lock
деструктор, который происходит при продолжении сопрограммы и в другом потоке (кроме того, на котором он был изначально заблокирован).
Использование Visual C ++ 2017.
использование std::atomic_bool
:
std::atomic_bool isRunning = false;
if (isRunning.exchange(true, std::memory_order_acq_rel) == false){
try{
auto result = co_await Refresh();
isRunning.store(false, std::memory_order_release);
//use result
}
catch(...){
isRunning.store(false, std::memory_order_release);
throw;
}
}
Два возможных улучшения: упаковка isRunning.store
в классе RAII и использовать std::shared_ptr<std::atomic_bool>
если время жизни, если atomic_bool
находится в области видимости
Других решений пока нет …