Исключение нарушения параллельного доступа winrt C++/cx

Что я пытаюсь сделать, так это проверить наличие файла в локальной папке, а затем скопировать его туда, если он не найден (файл ранее был добавлен в проект как актив).

Вот код:

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

}

Что происходит, так это то, что я получаю исключение нарушения доступа в точке, где «файл» назначается «fileVar» (возможно, из-за межпотокового доступа?). Как это исправить?

Редактировать: я не могу выполнить всю обработку там, потому что к файлу будут обращаться много раз. Короче говоря, мне нужно знать, когда он был успешно скопирован, и получить к нему доступ. Вот код, который работает

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

person seggaeman    schedule 24.08.2014    source источник
comment
Я также пытался сделать это с помощью специальных расширений WinRT (IAsyncOperation), и в этом случае все работает нормально.   -  person seggaeman    schedule 24.08.2014
comment
Почему вы пытаетесь зафиксировать значение файла в переменной fileVar? Разве вы не можете просто выполнить любую обработку, которую вам нужно сделать, с файлом внутри самой лямбда-выражения? Кроме того, я не понимаю вашего комментария по поводу IAsyncOperation. Не могли бы вы вставить код, который работает для вас?   -  person Raman Sharma    schedule 28.08.2014


Ответы (2)


Вместо того, чтобы передавать делегат в качестве аргумента и возвращать void, сделайте так, чтобы ваш метод возвращал task<StorageFile^>, а затем вызывающая сторона может выполнить .then(), чтобы продолжить работу после успешного завершения операции.

Или, если это отображается как общедоступный метод WinRT (а не внутренний/частный метод C++), используйте IAsyncOperation<StorageFile^>^ в качестве возвращаемого типа и оберните все это в create_async():

IAsyncOperation<StorageFile^>^ DoStuff(params)
{
  return concurrency::create_async([params]
  { 
    // function body goes here
  });
}
person Peter Torr - MSFT    schedule 03.11.2014

Вот решение, которое я собрал. Две вещи, которые важно знать:

  1. При выполнении асинхронной операции с использованием concurrency::create_task асинхронная операция может продолжать выполняться при возврате родительской функции. Таким образом, захваченные переменные ДОЛЖНЫ пережить контекст родительской функции. Чего, очевидно, не произойдет, если они передаются по ссылке. Потребовалось время, чтобы осознать это.

  2. WinRT накладывает определенные ограничения на среду выполнения с параллелизмом. Вызов concurrency::task::get() или concurrency::task::wait() вызовет исключение в потоке STA, если вызов не находится в продолжении задачи.

Дополнительная информация в этом сообщении: http://social.msdn.microsoft.com/Forums/windowsapps/en-US/ae54980b-41ce-4337-a059-2213b549be4b/concurrencyinvalidoperation-when-calling-tasktget?forum=winappswithnativecode

В таком случае, как узнать, когда функция завершила свою работу? Я решил передать обратный вызов (делегат AKA).

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);
                });
            });
        });
    }
});
}
person seggaeman    schedule 27.09.2014