并发任务从不执行 "then()" 也不会抛出异常(C++/CX UWP)
Concurrency task never execute "then()" nor throws exception (C++/CX UWP)
我在使用 Concurrency::task 从我的代码中列出一些设备时遇到问题:
去年使用此代码 visual studio 2015 年:
Concurrency::create_task(Windows::Devices::Enumeration::DeviceInformation::FindAllAsync(Windows::Devices::Midi::MidiInPort::GetDeviceSelector()))
.then([](Windows::Devices::Enumeration::DeviceInformationCollection ^midiDeviceCollection) {
for (Windows::Devices::Enumeration::DeviceInformation^ portInformation : midiDeviceCollection){
//do stuff with device
}
});
但是几个月后,...今天,我必须更新我的应用程序(同时我切换到 VS2017)。所以我重新打开并更新了项目,遗憾的是这段代码不再按应有的方式工作。 "then()" 部分中设置的断点永远不会到达,并且代码会继续执行而不会出错,就好像我的任务从未被调用过一样。
我想知道在执行 DeviceInformation::FindAllAsync 时是否出错,没有让 "then()" 部分变成 运行。在对任务 (https://msdn.microsoft.com/fr-fr/library/dd492427.aspx) 进行一些研究后,我找到了一种在任务 运行ning 时捕获异常的方法,因此我将我的代码 运行 修改为:
IAsyncOperation<DeviceInformationCollection^>^ deviceOp = DeviceInformation::FindAllAsync(MidiInPort::GetDeviceSelector());
Concurrency::task<DeviceInformationCollection^> deviceEnumTask = Concurrency::create_task(deviceOp);
deviceEnumTask.then([](Concurrency::task<DeviceInformationCollection^> t){
try{
DeviceInformationCollection ^midiDeviceCollection = t.get();
for (DeviceInformation^ portInformation : midiDeviceCollection) {
//do stuff with device
}
}catch (Platform::Exception^ e){
//do stuff with device
OutputDebugString(e->Message->Data());
}
});
相同的结果,应用程序从未进入 "then()" 并且没有抛出异常:-( .
所以我的问题是:这里有什么问题?这段代码是列出设备的最常见方式,我在其他网站的几个地方都看到过它(而且它去年在我的案例中有效),所以我应该在其他地方犯了错误或遗漏了一些东西。
这是捕捉任务错误的好方法吗?
更多信息:此代码直接在 MainPage 构造函数中调用,就在 InitializeComponent() 之后; .我是 运行ning VS2017,目标平台最低版本是 10.0.10240.0,目标平台版本是 10.0.15063.0。该应用程序是在桌面 x64 机器上构建和测试的。
非常感谢您的帮助。
编辑:
感谢 Sunteen Wu 和 David Pritchard(见下文),我已经能够找出问题的根源。上面的代码实际上有效,如答案中所述,并且在按照 David 的建议调整错误处理后非常清晰和完整。在我的测试中,我的断点也被放置在正确的线上。但实际上,我在没有意识到的情况下,在“//do stuff with device”(此处不可见)中更深入地使用了一些阻塞代码。这阻碍了 MainPage 构造函数,避免它继续执行,直到调用的任务完成......显然任务仍然与主线程执行相关联,并且只要 MainPage 构造没有结束就不会 运行 ,在我的情况下是什么从未发生 -> 死锁 !
Same result, the app never enters in "then()" and no Exception is thrown
通过我这边的测试,在task::then
里面设置断点时,它的delegate可以成功执行如下:
应用确实进入了委托。如果您在 task::then
代码行设置断点并使用 F10
跳过,它将跳过委托,因为 task::then
方法 returns 立即,它的委托运行 直到异步工作完成 successfully.Details 请参考 Consuming an async operation by using a task。
Is this the good way to catch errors in tasks ?
根据 Handling errors in a task chain,您尝试捕获错误的方式似乎是正确的。如果异步操作导致抛出异常,continuation 将永远不会执行。您将获得 task::get
传送到任务的所有异常。
你也可以看看"no longer work"是不是没有MidiInPort
造成的。尝试获取如上图所示的设备尺寸。
我无法回答你问题中的 MIDI 部分。我可以谈谈异常处理代码。
首先 - 我怀疑您看到的是来自 FindAllAsync
的异常,因为未观察到的异常通常只会使应用程序崩溃(或中断调试器)。
其次 - 上面的异常处理程序似乎混淆了两种类型的延续:
类型 1: 常规延续,继续工作一次异步方法 returns 其结果。
例如,deviceEnumTask.then([] (DeviceInformationCollection ^) { ... })
类型 2:异常处理延续,处理任务链中的任何异常。
例如,deviceEnumTask.then([] (Concurrency::task<void> t) { ... })
您的原始代码示例使用了类型 1 但没有包含类型 2,因此无法处理任何异常。
您修改后的代码示例使用了非标准延续(既不是类型 1 也不是类型 2),并且可能永远不会被调用。
你想要的是同时使用 1 和 2,就像这样:
using namespace Windows::Devices::Enumeration;
using namespace Windows::Devices::Midi;
Concurrency::create_task(
DeviceInformation::FindAllAsync(MidiInPort::GetDeviceSelector())
).then([](DeviceInformationCollection ^midiDeviceCollection) {
for (DeviceInformation^ portInformation : midiDeviceCollection){
//do stuff with device
}
}).then([] Concurrency::task<void> t) {
try {
t.get();
OutputDebugString(L"No exceptions seen");
}
catch (Platform::Exception ^e) {
OutputDebugString(e->Message->Data());
}
});
我在使用 Concurrency::task 从我的代码中列出一些设备时遇到问题:
去年使用此代码 visual studio 2015 年:
Concurrency::create_task(Windows::Devices::Enumeration::DeviceInformation::FindAllAsync(Windows::Devices::Midi::MidiInPort::GetDeviceSelector()))
.then([](Windows::Devices::Enumeration::DeviceInformationCollection ^midiDeviceCollection) {
for (Windows::Devices::Enumeration::DeviceInformation^ portInformation : midiDeviceCollection){
//do stuff with device
}
});
但是几个月后,...今天,我必须更新我的应用程序(同时我切换到 VS2017)。所以我重新打开并更新了项目,遗憾的是这段代码不再按应有的方式工作。 "then()" 部分中设置的断点永远不会到达,并且代码会继续执行而不会出错,就好像我的任务从未被调用过一样。
我想知道在执行 DeviceInformation::FindAllAsync 时是否出错,没有让 "then()" 部分变成 运行。在对任务 (https://msdn.microsoft.com/fr-fr/library/dd492427.aspx) 进行一些研究后,我找到了一种在任务 运行ning 时捕获异常的方法,因此我将我的代码 运行 修改为:
IAsyncOperation<DeviceInformationCollection^>^ deviceOp = DeviceInformation::FindAllAsync(MidiInPort::GetDeviceSelector());
Concurrency::task<DeviceInformationCollection^> deviceEnumTask = Concurrency::create_task(deviceOp);
deviceEnumTask.then([](Concurrency::task<DeviceInformationCollection^> t){
try{
DeviceInformationCollection ^midiDeviceCollection = t.get();
for (DeviceInformation^ portInformation : midiDeviceCollection) {
//do stuff with device
}
}catch (Platform::Exception^ e){
//do stuff with device
OutputDebugString(e->Message->Data());
}
});
相同的结果,应用程序从未进入 "then()" 并且没有抛出异常:-( .
所以我的问题是:这里有什么问题?这段代码是列出设备的最常见方式,我在其他网站的几个地方都看到过它(而且它去年在我的案例中有效),所以我应该在其他地方犯了错误或遗漏了一些东西。
这是捕捉任务错误的好方法吗?
更多信息:此代码直接在 MainPage 构造函数中调用,就在 InitializeComponent() 之后; .我是 运行ning VS2017,目标平台最低版本是 10.0.10240.0,目标平台版本是 10.0.15063.0。该应用程序是在桌面 x64 机器上构建和测试的。
非常感谢您的帮助。
编辑:
感谢 Sunteen Wu 和 David Pritchard(见下文),我已经能够找出问题的根源。上面的代码实际上有效,如答案中所述,并且在按照 David 的建议调整错误处理后非常清晰和完整。在我的测试中,我的断点也被放置在正确的线上。但实际上,我在没有意识到的情况下,在“//do stuff with device”(此处不可见)中更深入地使用了一些阻塞代码。这阻碍了 MainPage 构造函数,避免它继续执行,直到调用的任务完成......显然任务仍然与主线程执行相关联,并且只要 MainPage 构造没有结束就不会 运行 ,在我的情况下是什么从未发生 -> 死锁 !
Same result, the app never enters in "then()" and no Exception is thrown
通过我这边的测试,在task::then
里面设置断点时,它的delegate可以成功执行如下:
应用确实进入了委托。如果您在 task::then
代码行设置断点并使用 F10
跳过,它将跳过委托,因为 task::then
方法 returns 立即,它的委托运行 直到异步工作完成 successfully.Details 请参考 Consuming an async operation by using a task。
Is this the good way to catch errors in tasks ?
根据 Handling errors in a task chain,您尝试捕获错误的方式似乎是正确的。如果异步操作导致抛出异常,continuation 将永远不会执行。您将获得 task::get
传送到任务的所有异常。
你也可以看看"no longer work"是不是没有MidiInPort
造成的。尝试获取如上图所示的设备尺寸。
我无法回答你问题中的 MIDI 部分。我可以谈谈异常处理代码。
首先 - 我怀疑您看到的是来自 FindAllAsync
的异常,因为未观察到的异常通常只会使应用程序崩溃(或中断调试器)。
其次 - 上面的异常处理程序似乎混淆了两种类型的延续:
类型 1: 常规延续,继续工作一次异步方法 returns 其结果。
例如,
deviceEnumTask.then([] (DeviceInformationCollection ^) { ... })
类型 2:异常处理延续,处理任务链中的任何异常。
例如,
deviceEnumTask.then([] (Concurrency::task<void> t) { ... })
您的原始代码示例使用了类型 1 但没有包含类型 2,因此无法处理任何异常。
您修改后的代码示例使用了非标准延续(既不是类型 1 也不是类型 2),并且可能永远不会被调用。
你想要的是同时使用 1 和 2,就像这样:
using namespace Windows::Devices::Enumeration;
using namespace Windows::Devices::Midi;
Concurrency::create_task(
DeviceInformation::FindAllAsync(MidiInPort::GetDeviceSelector())
).then([](DeviceInformationCollection ^midiDeviceCollection) {
for (DeviceInformation^ portInformation : midiDeviceCollection){
//do stuff with device
}
}).then([] Concurrency::task<void> t) {
try {
t.get();
OutputDebugString(L"No exceptions seen");
}
catch (Platform::Exception ^e) {
OutputDebugString(e->Message->Data());
}
});