并发任务从不执行 "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());
   }
});