Search code examples
concurrencyc++-cxwinrt-async

winrt c++/cx concurrency access violation exception


What I'm trying to do is check for the existence of a file in the local folder and then copy it there if it isn't found (the file was previously added to the project as an asset).

Here is the code:

Windows::Storage::StorageFile^ MainPage::GetCustomFileAsync(Platform::String^ fileName)
{
using Windows::Storage::StorageFile;
using Windows::Storage::StorageFolder;

auto localFolder = Windows::Storage::ApplicationData::Current->LocalFolder;
auto localTask = concurrency::create_task(localFolder->GetFileAsync(fileName));
StorageFile^ retVal = nullptr;
localTask.then([&](StorageFile^ t){
    retVal = t;
}).then([](concurrency::task<void> t)
{
    try
    {
        t.get();
        OutputDebugString(L"Found\n");
    }
    catch (Platform::COMException^ e)
    {
        OutputDebugString(e->Message->Data());
    }
}).wait();
return retVal;
}


StorageFile^ fileVar;
if ((fileVar = this->GetCustomFileAsync("somefile.txt")) == nullptr)
{
    String^ path = Windows::ApplicationModel::Package::Current->InstalledLocation->Path + "\\Assets";
    concurrency::create_task(Windows::Storage::StorageFolder::GetFolderFromPathAsync(path)).then([](StorageFolder^ folder){
        return (folder->GetFileAsync("somefile.txt"));
    }).then([](StorageFile^ file){
        return (file->CopyAsync(Windows::Storage::ApplicationData::Current->LocalFolder));
    }).then([&](StorageFile^ file){
        fileVar = file;
        OutputDebugString(file->DisplayName->Data());
    });

}

What happens is that I get an access violation exception at the point where "file" is being assigned to "fileVar" (because of cross-thread access perhaps?). How to fix this?

Edit: I can't do all the processing there because the file will be accessed many times. In short I need to know when it has been successfully copied and get a handle to it. Here is the code that works

Windows::Storage::StorageFile^ GetFile(Platform::String^ fileName)
{
using Windows::Storage::StorageFile;
using Windows::Storage::StorageFolder;
using Windows::Foundation::AsyncOperationCompletedHandler;
using Windows::Foundation::AsyncStatus;
using Windows::Foundation::IAsyncOperation;
using Platform::String;

auto localFolder = Windows::Storage::ApplicationData::Current->LocalFolder;
bool completed = false;
StorageFile^ retVal = nullptr;
localFolder->GetFileAsync(fileName)->Completed = ref new AsyncOperationCompletedHandler<StorageFile^>([&completed, &retVal, &fileName](IAsyncOperation<StorageFile^>^ fileOperation, AsyncStatus status)
{
    if (status == AsyncStatus::Error)
    {
        String^ path = Windows::ApplicationModel::Package::Current->InstalledLocation->Path + "\\Assets";
        Windows::Storage::StorageFolder::GetFolderFromPathAsync(path)->Completed = ref new AsyncOperationCompletedHandler<Windows::Storage::StorageFolder^>(
            [&completed, &retVal, &fileName](IAsyncOperation<Windows::Storage::StorageFolder^>^ folderOperation, AsyncStatus status)->void{
            auto assetFolder = folderOperation->GetResults();
            assetFolder->GetFileAsync(fileName)->Completed = ref new AsyncOperationCompletedHandler<Windows::Storage::StorageFile^>([&completed, &retVal, &fileName](IAsyncOperation<Windows::Storage::StorageFile^>^ fileOperation, AsyncStatus status)->void{
                auto file = fileOperation->GetResults();
                file->CopyAsync(Windows::Storage::ApplicationData::Current->LocalFolder)->Completed = ref new AsyncOperationCompletedHandler<Windows::Storage::StorageFile^>
                    ([&completed, &retVal, &fileName](IAsyncOperation<Windows::Storage::StorageFile^>^ fileOperation, AsyncStatus status)->void {
                    retVal = fileOperation->GetResults();
                    completed = true;
                });
            });
        });
    }
    else
    {
        retVal = fileOperation->GetResults();
        completed = true;
    }
});
while (completed == false);
return retVal;
}

Solution

  • Here's a solution I put together. Two things that are important to know:

    1. When executing an asynchronous operation using concurrency::create_task the async operation(s) can still be executing when the parent function returns. So the captured variables MUST outlive the context of the parent function. Which obviously won't happen if they are being passed by reference. It took a while to realize this.

    2. WinRT imposes certain restrictions on the concurrency runtime. Calling concurrency::task::get() or concurrency::task::wait() will throw an exception in an STA thread, unless the call is in a task continuation.

    More information in this post: http://social.msdn.microsoft.com/Forums/windowsapps/en-US/ae54980b-41ce-4337-a059-2213b549be4b/concurrencyinvalidoperation-when-calling-tasktget?forum=winappswithnativecode

    In that case how to know when the function has finished doing it's job? I opted to pass in a callback (AKA delegate).

    delegate void FileOperation(Windows::Storage::StorageFile^ file);
    void GetFileConcurrency(Platform::String^ fileName, FileOperation^ fileOp)
    {
    using Windows::Storage::StorageFile;
    using Windows::Storage::StorageFolder;
    using Platform::String;
    
    auto localFolder = Windows::Storage::ApplicationData::Current->LocalFolder;
    String^ assetFolderPath = Windows::ApplicationModel::Package::Current->InstalledLocation->Path + "\\Assets";
    
    
    auto localFolderTask = concurrency::create_task(localFolder->GetFileAsync(fileName));
    localFolderTask.then([localFolder, assetFolderPath, fileName, fileOp](concurrency::task<StorageFile^> theTask){
        try
        {
            StorageFile^ theFile = theTask.get();
            fileOp(theFile);
        }
        catch (Platform::Exception^ e)
        {
            OutputDebugString(e->Message->Data());
            auto assetFolderTask = concurrency::create_task(StorageFolder::GetFolderFromPathAsync(assetFolderPath));
            assetFolderTask.then([localFolder, assetFolderPath, fileName, fileOp](StorageFolder^ assetFolder){
                auto assetFileTask = concurrency::create_task(assetFolder->GetFileAsync(fileName));
                assetFileTask.then([localFolder, assetFolderPath, fileName, fileOp](StorageFile^ file){
                    auto copyFileTask = concurrency::create_task(file->CopyAsync(localFolder));
                    copyFileTask.then([localFolder, assetFolderPath, fileName, fileOp](StorageFile^ file){
                        OutputDebugString(file->Path->Data());
                        fileOp(file);
                    });
                });
            });
        }
    });
    }