Нужно ли синхронизировать мой деструктор с асинхронными функциями при использовании 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?


person Craig    schedule 12.04.2018    source источник


Ответы (1)


Если кто-то явно не пытается delete объект, все будет в порядке, поскольку лямбда-выражение захватывает указатель 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 не приводит к сбою — хотя это поведение undefined — но если вы попытаетесь получить доступ к элементам data, вы получите исключение (или хуже)).

person Peter Torr - MSFT    schedule 18.04.2018