并发任务永远不会执行"然后()"也抛出异常(C ++ / CX UWP)

时间:2018-01-11 10:37:13

标签: exception concurrency uwp task c++-cx

使用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)。所以我重新打开并更新了项目,遗憾的是这段代码不再按照应有的方式运行。断点设置在"然后()"部件永远不会到达,代码继续执行而不会出现错误,就好像我的任务从未被调用过一样。

我想知道执行DeviceInformation :: FindAllAsync时是否是一个错误,它不会让" then()"部分运行。在对任务(https://msdn.microsoft.com/fr-fr/library/dd492427.aspx)进行一些研究之后,我找到了一种在任务运行时捕获异常的方法,因此我将代码转换为:

    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());
        }

    });

同样的结果,应用程序永远不会进入&#34;然后()&#34;并且没有抛出异常:-(。

所以我的问题是:这里有什么问题?这段代码是列出设备最常用的方式,我已经看到它在几个地方的所有其他网页(而且它的工作在去年我的情况),所以我应该犯了一个错误或遗漏其他地方的东西。

这是捕捉任务错误的好方法吗?

更多信息:这个代码直接在MainPage构造函数中调用,就在InitializeComponent()之后; 。我正在运行VS2017,目标平台最低版本是10.0.10240.0,目标平台版本是10.0.15063.0。该应用程序是在台式机x64机器上构建和测试的。

非常感谢你的帮助。

修改:

感谢Sunteen Wu和David Pritchard(见下文),我已经能够找出问题的来源。上面的代码实际上是有效的,如答案中所述,并且在调整David提出的错误处理之后非常清楚和完整。在我的测试中,我的断点也放在了正确的位置。但实际上,在没有意识到的情况下,我在&#34; //中使用了一些阻塞代码更深入了解设备&#34; (这里不可见)。这使得MainPage构造函数无法继续执行,直到被调用的任务完成为止...显然任务仍然与主线程执行相关联,并且只要MainPage构造没有结束就没有运行,在我的情况下从未发生过 - &gt ;死锁!

2 个答案:

答案 0 :(得分:0)

  

同样的结果,应用程序永远不会进入“then()”并且不会抛出异常

通过测试,在task::then内设置断点时,其委托可以成功执行,如下所示: enter image description here

应用程序确实输入了委托。如果您仅在task::then代码行设置断点并使用F10跳过,它将跳过委托,因为task::then方法立即返回,并且其委托不会运行直到异步工作成功完成。详细请参考Consuming an async operation by using a task

  

这是捕捉任务错误的好方法吗?

根据Handling errors in a task chain,您尝试捕捉错误的方式似乎是正确的。如果异步操作导致抛出异常,则永远不会执行继续。您将获得task::get传输到任务的任何例外情况。

您还可以检查没有MidiInPort是否导致“不再工作”。尝试获取设备大小如上图所示。

答案 1 :(得分:0)

我无法与你问题的MIDI部分说话。我可以使用异常处理代码。

首先 - 我怀疑你是否看到来自FindAllAsync的异常,因为未观察到的异常通常会使应用程序崩溃(或者破解为调试程序)。

第二 - 上面的异常处理程序似乎混淆了两种类型的延续:

  • 类型1:常规延续,一旦异步方法返回其结果,就继续工作。

    例如,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());
   }
});