使用 PPL 时是否需要将我的析构函数与异步函数同步?
Do I need to synchronize my destructor with async functions when using PPL?
假设我有一个 ViewModel,当用户离开其绑定的 View 时可以将其销毁。析构函数对订阅成员变量执行清理:
MyViewModel::~MyViewModel()
{
if (m_subscription)
{
if (m_contentChangedToken.Value != 0)
{
m_subscription->ContentChanged -= m_contentChangedToken;
m_contentChangedToken.Value = 0;
}
}
}
创建 ViewModel 后,异步获取订阅的函数 运行s,将其分配给成员变量,并分配事件侦听器
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 的原因析构函数总是最后的吗?
除非有人明确尝试 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();
}
当你 运行 它时,你会在输出中看到类似这样的东西 window:
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
的元素,您将得到一个异常(或更糟))。
假设我有一个 ViewModel,当用户离开其绑定的 View 时可以将其销毁。析构函数对订阅成员变量执行清理:
MyViewModel::~MyViewModel()
{
if (m_subscription)
{
if (m_contentChangedToken.Value != 0)
{
m_subscription->ContentChanged -= m_contentChangedToken;
m_contentChangedToken.Value = 0;
}
}
}
创建 ViewModel 后,异步获取订阅的函数 运行s,将其分配给成员变量,并分配事件侦听器
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 的原因析构函数总是最后的吗?
除非有人明确尝试 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();
}
当你 运行 它时,你会在输出中看到类似这样的东西 window:
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
的元素,您将得到一个异常(或更糟))。