locked
Total confusion with tasks, GetFolderAsync/CreateFolderAsync, wait() and try/catch (C++)

    Question

  • I don't get anything consistent with that code below, which is the first things I do in App::OnLaunched.

    When launched in the debugger, sometimes it works, sometimes it doesn't... I should say most of te times it doesn't.

    I mean if I start with a non deployed scenario, sometimes the subfolder in local appdata is created and m_subfolder gets its value, sometimes not and it goes through the catch(...). Likewise, after first deployment, the fodler already exists but still the GetFodlerAsync sometimes fails, sometimes not.

    I tried without the wait() but of course this is worse, as the tasks which should set its value are not even supposed to finish their work before the next part of code goes on.

    What am I doing or assuming wrong ?

    StorageFolder^ localFolder = ApplicationData::Current->LocalFolder;
    try {
    	create_task(localFolder->GetFolderAsync(L"subfolder")).then([this](StorageFolder^ f) { 
    		m_subfolder = f; 
    	}).wait();
    }
    catch(...)
    {
    	try {
    		create_task(localFolder->CreateFolderAsync(L"subfolder")).then([this](StorageFolder^ f){ 
    			m_subfolder = f; 
    		}).wait();
    	}
    	catch(...)
    	{
    		m_subfolder=nullptr;
    	}
    };

    Friday, June 15, 2012 3:23 PM

Answers

  • Waiting on an asynchronous operation on the UI thread via a call to <task>.wait() or <task>.get() isn't allowed as it can make the UI unresponsive.  If the async operation has not completed, calling .wait() or .get() on the UI thread will throw an exception indicating this.  This is probably why you are seeing this succeed sometimes but mostly not.  The operations which depend on m_subfolder should be in a continuation (or disallowed/not available until the continuation runs). 

    Friday, June 15, 2012 4:49 PM
  • Continuations which are scheduled on tasks which encapsulate an async response are, by default, moved back to the apartment where the continuation was scheduled when they are ready to run.  If you are running this code on the UI thread, when the GetFolderAsync call or CreateFolderAsync call complete, they will try to get the continuation (the lambda you've passed into .then) back to the UI thread.  If the UI thread is blocking in this manner, the continuation will be scheduled to run on the UI thread but never get to.  The two threads will deadlock.

    Running in a continuation after the asynchronous operation completes is the intended way to utilize the APIs.  This avoids the UI becoming unresponsive and avoids Windows thinking the application is hung.

    What about what you're trying to do makes it difficult to put in a continuation on the operation?

    Monday, June 18, 2012 5:52 PM

All replies

  • Waiting on an asynchronous operation on the UI thread via a call to <task>.wait() or <task>.get() isn't allowed as it can make the UI unresponsive.  If the async operation has not completed, calling .wait() or .get() on the UI thread will throw an exception indicating this.  This is probably why you are seeing this succeed sometimes but mostly not.  The operations which depend on m_subfolder should be in a continuation (or disallowed/not available until the continuation runs). 

    Friday, June 15, 2012 4:49 PM
  • Thanks the catch() makes more sense to me now.

    This morning I tried the following code, which ends by waiting for an event. However, this proves to work only by chance and only because GetFolder is very fast. On my advice, one of my programmers used this technic to put a WaitForSingleObjectEx at the end of a ReadAsync operation, this doesn't work as the WaitForSingleObjectEx seems to forbid the ReadAsync to proceed, and then the Wait waits forever.

    If the only way to proceed after an async operation is in a .then(), this will be a big problem to the old code I'm trying to translate into metro style...

    Here's the code that doesn't work correctly.

    StorageFolder^ localFolder = ApplicationData::Current->LocalFolder;
    auto folderok=CreateEventEx(NULL,NULL,CREATE_EVENT_MANUAL_RESET, DELETE|SYNCHRONIZE|EVENT_MODIFY_STATE);
    try {
    	create_task(localFolder->GetFolderAsync(L"subfolder")).then([this](StorageFolder^ f) { 
    		m_subfolder = f; 
    		SetEvent(folderok);
    	}).wait();
    }
    catch(...)
    {
    	try {
    		create_task(localFolder->CreateFolderAsync(L"subfolder")).then([this](StorageFolder^ f){ 
    			m_subfolder = f; 
    			SetEvent(folderok);
    		}).wait();
    	}
    	catch(...)
    	{
    		m_subfolder=nullptr;
    		SetEvent(fodlerok);
    	}
    };
    WaitForSingleObjectEx(folderok,INFINITE,FALSE);
    CloseHandle(folderok);



    Monday, June 18, 2012 8:16 AM
  • Continuations which are scheduled on tasks which encapsulate an async response are, by default, moved back to the apartment where the continuation was scheduled when they are ready to run.  If you are running this code on the UI thread, when the GetFolderAsync call or CreateFolderAsync call complete, they will try to get the continuation (the lambda you've passed into .then) back to the UI thread.  If the UI thread is blocking in this manner, the continuation will be scheduled to run on the UI thread but never get to.  The two threads will deadlock.

    Running in a continuation after the asynchronous operation completes is the intended way to utilize the APIs.  This avoids the UI becoming unresponsive and avoids Windows thinking the application is hung.

    What about what you're trying to do makes it difficult to put in a continuation on the operation?

    Monday, June 18, 2012 5:52 PM
  • Thank you.!

    I came to the conclusions that you're detailling, they reminded me old issues with blocking main-thread problems in MFC applications. I'm still a little confused by the async/tasks/UI threads issues and/or C++/CX syntax but it starts to make sense to me. In April 2012 I thought I was a C++ programmer, now I'm not so sure anymore :-)

    My problem is not using a continuation, it's that the Library I'm porting is mostly synchonous and all functions are supposed to return their result, not just return with result "on the way". As an example here the UI thread will go on while my folder is not necessarily ready yet : possibly another work will start from the UI immediately after OnLaunched returns, implying the use of this folder. Which would be nullptr. Which would crash the app. Which is bad. Also the continuation in my previous code is not practical, as it would not cross the try/catch structures which are supposed to create the folder when it doesn't exists... I can certainly make something better.

    (I also discovered this morning only that deploying the app or building release would delete my appdata folder, and make my GetFolderAsync() + GetFileAsync() fail. And confusing me furthermore when launching debug and oooh noooo crashing again.)

    In the end, I decided to move this code into a function returning a task<StorageFolder^>. I will call this function in OnLaunched(), not doing any special continuation, and call it again at first for the other jobs on the app, this time doing the work into a then(). I will transform my synchronous code portions into sequences of task().then() like in the Microsoft samples. And modify all my "synchronous" functions into functions returning tasks, so the application can use continuations on them.

    I understand now that every async operations results can only be used into a then() continuation, although I could certainly use the thread pool so I can use .wait() and keep somewhat synchronous code sequences while not blocking the UI ... I will check this if continuations do not solve my problems.

    Another problem, but you can't help, is that my deadline was obviously based on the fact that moving to Metro would not imply too much refactoring. Took me two weeks to change thousands of char* strings and members and function parameters into wchar_t* ones already :-)

    Tuesday, June 19, 2012 2:10 PM