次の方法で共有


How to debug a task exception in Windows Store application

Author: Han Xia

Being fast and fluent is the one of the key design principles of developing Windows Store applications. It makes asynchronous programming ubiquitous in this platform. However, when you are using C++ to develop Windows Store apps with asynchronous manner, you may encounter some unobserved task exceptions that are hard to find the root cause. In this post, I would like to discuss how to debug a task exception in Windows Store application.

Let’s use a sample to illustrate the detail steps. I create a simple project that uses asynchronous API to pick up a jpg file and show it. The code snippet shows as following:

Xaml code:

    <StackPanel Background="{StaticResource ApplicationPageBackgroundThemeBrush}">

        <Button x:Name="btnPicker" Content="Open File..." Click="Picker"/>

        <TextBlock x:Name="txtBlockOutput" Width="100"/>

        <Image x:Name="ImageFromFile" Stretch="Fill"/>

</StackPanel>

C++ code:

void WindowsRTPicker::MainPage::Picker(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)

{

                FileOpenPicker^ openPicker = ref new FileOpenPicker();

                openPicker->ViewMode = PickerViewMode::Thumbnail;

                openPicker->SuggestedStartLocation = PickerLocationId::PicturesLibrary;

                openPicker->FileTypeFilter->Append(".jpg");

                create_task(openPicker->PickSingleFileAsync()) //get the file

                .then([this](StorageFile^ file)

                {

                                txtBlockOutput->Text = "Picked photo: " + file->Name;

                                create_task(file->OpenAsync(FileAccessMode::Read)). //open the file

                                then([=](IRandomAccessStream^ bitmapStream)

                                {

                                                create_task([this](){

                                               

                                                                wait(1000); //emulate an expensive task;

                                                               

                                                }).then([=]()

                                                {

                                                                BitmapImage^ bitmapImage = ref new BitmapImage();

                                                                bitmapImage->SetSource(bitmapStream);

                                                                ImageFromFile->Source = bitmapImage;

                                                                                                                               

                                                },task_continuation_context::use_current());

                                               

                                });

                });

}

However, this code has something wrong. When you debug the project which includes above code in Visual Studio 2012, it will prompt an unhandled exception shown as following:

 

The exception message says that an invalid parameter was passed to a function that considers invalid parameters fatal. It doesn’t make sense. And the call stack also cannot give us any clue:

msvcr110d.dll!0faa9ad1()

WindowsRTPicker.exe!Concurrency::details::_ExceptionHolder::~_ExceptionHolder() Line 882

WindowsRTPicker.exe!Concurrency::details::_ExceptionHolder::`scalar deleting destructor'(unsigned int)

WindowsRTPicker.exe!std::_Ref_count_obj<Concurrency::details::_ExceptionHolder>::_Destroy() Line 885

WindowsRTPicker.exe!std::_Ref_count_base::_Decref() Line 120

WindowsRTPicker.exe!std::_Ptr_base<Concurrency::details::_ExceptionHolder>::_Decref() Line 347

WindowsRTPicker.exe!std::shared_ptr<Concurrency::details::_ExceptionHolder>::~shared_ptr<Concurrency::details::_ExceptionHolder>() Line 624

WindowsRTPicker.exe!Concurrency::details::_Task_impl_base::~_Task_impl_base() Line 1294

WindowsRTPicker.exe!Concurrency::details::_Task_impl<unsigned char>::~_Task_impl<unsigned char>() Line 1972

WindowsRTPicker.exe!Concurrency::details::_Task_impl<unsigned char>::`scalar deleting destructor'(unsigned int)

WindowsRTPicker.exe!std::_Ref_count_obj<Concurrency::details::_Task_impl<unsigned char> >::_Destroy() Line 884

WindowsRTPicker.exe!std::_Ref_count_base::_Decref() Line 120

WindowsRTPicker.exe!std::_Ptr_base<Concurrency::details::_Task_impl<unsigned char> >::_Decref() Line 347

WindowsRTPicker.exe!std::shared_ptr<Concurrency::details::_Task_impl<unsigned char> >::~shared_ptr<Concurrency::details::_Task_impl<unsigned char> >() Line 624

WindowsRTPicker.exe!Concurrency::details::_PPLTaskHandle<unsigned char,Concurrency::task<unsigned char>::_ContinuationTaskHandle<void,void,<lambda_af0a206e09a66eb8e507263ce4c9dbe9>,std::integral_constant<bool,0>,Concurrency::details::_TypeSelectorNoAsync>,Concurrency::details::_ContinuationTaskHandleBase>::~_PPLTaskHandle<unsigned char,Concurrency::task<unsigned char>::_ContinuationTaskHandle<void,void,<lambda_af0a206e09a66eb8e507263ce4c9dbe9>,std::integral_constant<bool,0>,Concurrency::details::_TypeSelectorNoAsync>,Concurrency::details::_ContinuationTaskHandleBase>() Line 1200

WindowsRTPicker.exe!Concurrency::task<unsigned char>::_ContinuationTaskHandle<void,void,<lambda_af0a206e09a66eb8e507263ce4c9dbe9>,std::integral_constant<bool,0>,Concurrency::details::_TypeSelectorNoAsync>::~_ContinuationTaskHandle<void,void,<lambda_af0a206e09a66eb8e507263ce4c9dbe9>,std::integral_constant<bool,0>,Concurrency::details::_TypeSelectorNoAsync>() Line 3313

WindowsRTPicker.exe!Concurrency::task<unsigned char>::_ContinuationTaskHandle<void,void,<lambda_af0a206e09a66eb8e507263ce4c9dbe9>,std::integral_constant<bool,0>,Concurrency::details::_TypeSelectorNoAsync>::`scalar deleting destructor'(unsigned int)

msvcr110d.dll!0fa730bc()

msvcr110d.dll!0faa9721()

msvcr110d.dll!0fa73afa()

msvcr110d.dll!0fa83063()

msvcr110d.dll!0faa4bc1()

msvcr110d.dll!0fa7fd07()

msvcr110d.dll!0fa7fbd3()

msvcr110d.dll!0fa7e65c()

msvcr110d.dll!0faabea2()

kernel32.dll!76c98543()

ntdll.dll!775dac69()

ntdll.dll!775dac3c()

 

There isn’t any user code involved in the call stack frames. However, we can find an interesting object from here:

WindowsRTPicker.exe!Concurrency::details::_ExceptionHolder::~_ExceptionHolder() Line 882

_ExceptionHolder is a wrapper which contains the original exception information. Let’s have a look at what the exception holder holds:

 

M_winRTException and _M_disassembleMe are two important member fields of the exception holder.

If your error is due to a Platform exception, the _M_winRTException field will be non-null. You can expand it to find the error code and the name of the exception from __hresult and __throwInfo fields. Here _hresult is 0x800100e and _throwInfo shows the WrongThread exception.

Now we know the real error information. We still want to know which line causes the issue. Let’s check the value from _M_disassembleMe field. Copy the address stored in thisfield (here is 0x00d78207), paste it into the Disassembly window and hit Enter:

This takes us to the instruction right after a call to task::then function.

Right click this instruction and select Go to Source Code:

Then you can see the real code which raises the exception.

We know the all the controls should update their UIs in the UI thread. However, here the asynchronous code makes continuation body executed on the work thread, which causes the WrongThread exception.

So we can change the code to dispatch the UI update code into the UI thread:

auto dispatcher=CoreWindow::GetForCurrentThread()->Dispatcher;

create_task(file->OpenAsync(FileAccessMode::Read)). //open the file

then([=](IRandomAccessStream^ bitmapStream)

{

    create_task([this]()

    { 

       wait(1000); //emulate a cost operation;

    })

    .then([=]()

    {

       dispatcher->RunAsync(CoreDispatcherPriority::Normal,

ref new Windows::UI::Core::DispatchedHandler(

                     [=]()

{

                  BitmapImage^  bitmapImage = ref new BitmapImage();

                  bitmapImage->SetSource(bitmapStream);

                  ImageFromFile->Source = bitmapImage;

              }));

                    

    });

});

This time the code works fine now. But if you are careful enough, you may still have a question: The first continuation also update the control UI:

       .then([this](StorageFile^ file)

       {

              txtBlockOutput->Text = "Picked photo: " + file->Name;

               

              create_task(file->OpenAsync(FileAccessMode::Read)). //open the file

              then([=](IRandomAccessStream^ bitmapStream)

              {

                     create_task([this](){

                    

                           wait(1000); //emulate an expensive task;

Why doesn’t it raise the WrongThread exception? The reason is that the first task returns an asynchronous operation but the third one doesn’t. 

Usually, you should not make any assumption about the underlying threads your tasks will run on. The one exception is windows store applications. It allows that only the UI thread can touch UI components. If a continuation is created by the UI thread, the task will schedule the continuation back onto UI thread if there is a WinRT aynchronous operation on the continuation chain before the continuation. If a task doesn't return an asynchronous operation, then it's not apartment-aware. And, by default, its continuations are run on the first available background thread.

In this case, the first task is created with a WinRT asynchronous operation, and the continuation is created on the UI thread, so it will be scheduled back to UI thread. The third task doesn’t return an asynchronous operation, so its continuation will not be scheduled on the UI thread, which causes the WrongThread exception.  

In summary, this post illustrates the steps on how to troubleshoot an task exception issue. We can get the real exception information from _ExceptionHolder object. And it also shows a typical asynchronous issue which is caused by the invalid asynchronous context.

Comments

  • Anonymous
    June 17, 2013
    thanks for sharing your coment! if your coment doesn´t appear right away , please be patient as it may take a few minutes to publish or may require moderation.