【问题标题】:Do I need to synchronize my destructor with async functions when using PPL?使用 PPL 时是否需要将我的析构函数与异步函数同步?
【发布时间】:2018-04-12 00:44:13
【问题描述】:

假设我有一个 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 吗?

【问题讨论】:

    标签: c++-cx ppl


    【解决方案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 的元素,您会得到一个异常(或更糟)。

    【讨论】:

      猜你喜欢
      • 2017-05-15
      • 1970-01-01
      • 2015-03-12
      • 2020-05-14
      • 2018-09-10
      • 2012-07-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多