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.