使用PPL时是否需要将析构函数与异步函数同步?

时间:2018-04-12 00:44:14

标签: c++-cx ppl

假设我有一个ViewModel,当用户离开其绑定的View时可以销毁它。析构函数对订阅成员变量执行清理:

MyViewModel::~MyViewModel()
{
    if (m_subscription)
    {
        if (m_contentChangedToken.Value != 0)
        {
            m_subscription->ContentChanged -= m_contentChangedToken;
            m_contentChangedToken.Value = 0;
        }
    }
}

创建ViewModel后,将运行一个异步获取订阅的函数,将其分配给成员变量并分配事件侦听器

void MyViewModel::AwesomeFunctionAsync()
{
    create_task(TargetedContentSubscription::GetAsync(c_subId))
        .then([this](TargetedContentSubscription^ subscription)
    {
        if (subscription)
        {
            m_subscription = subscription;
            m_contentChangedToken = m_subscription->ContentChanged += // attach event
        }
    }, task_continuation_context::use_arbitrary());
}

现在假设我的ViewModel正在被销毁,而后台线程正在AwesomeFunctionAsync中运行代码。潜伏在这里的竞争条件是什么?例如,析构函数可能在后台线程附加事件之前运行吗?或者我可以相信析构函数总是因GC而持续吗?

1 个答案:

答案 0 :(得分:1)

除非有人明确地尝试delete对象,否则你会没事,因为lambda捕获this指针并保持活着状态。

例如,尝试以下简单测试:

ref struct TestClass sealed
{
  void DoStuffAsync()
  {
    concurrency::create_async([this]()
    {
      Sleep(1000);
      PrintValue();
    });
  }

  void PrintValue()
  {
    // Accessing 'name' after deletion is undefined behavior, but it 
    // "works on my machine" for the purposes of this demonstration.
    std::string message = name + ": PrintValue is running.";

    // Accessing 'data.size()' after deletion is also undefined behavior
    if (data.size() == 0)
    {
      message += " Oops, I'm about to crash\r\n";
    }
    else
    {
      message = message + " Data is " + std::to_string(data[0]) + 
        ", " + std::to_string(data[1]) + "\r\n";
    }

    OutputDebugStringA(message.c_str());
  }

  virtual ~TestClass()
  {
    std::string message = name + ": Destructor is running.\r\n";
    OutputDebugStringA(message.c_str());
  }

internal: // so we can use 'const char *'

  TestClass(const char* name) : name{ name }, data{ 1, 2 }
  {
    std::string message = this->name + ": Constructor is running.\r\n";
    OutputDebugStringA(message.c_str());
  }

private:
  std::string name;
  std::vector<int> data;
};

void Test()
{
  OutputDebugStringA("Starting 'no async' test\r\n");
  {
    auto c = ref new TestClass("no async");
    c->PrintValue();
  }
  OutputDebugStringA("---\r\nDone. Starting 'async' test\r\n");

  {
    auto c = ref new TestClass("async");
    c->DoStuffAsync();
  }
  OutputDebugStringA("---\r\nDone. Starting 'explicit delete' test\r\n");

  {
    auto c = ref new TestClass("explicit delete");
    c->DoStuffAsync();
    delete c;
  }
}


MainPage::MainPage()
{
  InitializeComponent();

  Test();
}

运行它时,您会在“输出”窗口中看到类似的内容:

Starting 'no async' test 
no async: Constructor is running. 
no async: PrintValue is running. Data is 1, 2 
no async: Destructor is running.
--- Done. Starting 'async' test 
async: Constructor is running.
--- Done. Starting 'explicit delete' test 
explicit delete: Constructor is running. 
explicit delete: Destructor is running. 
async: PrintValue is running. Data is 1, 2 
: PrintValue is running. Oops, I'm about to crash 
async: Destructor is running.

注意'async'版本在PrintValue异步运行之后才运行析构函数。但是'explicit delete'版本会破坏该对象,该对象在大约1秒后尝试访问成员变量时会崩溃。 (您可以看到访问name不会崩溃 - 尽管它是未定义的行为 - 但如果您尝试访问data的元素,您将获得异常(或更糟))。