В приложении UWP future.wait () продолжает ждать, пытаясь синхронизировать ответ от асинхронных методов - PullRequest
0 голосов
/ 16 января 2019

Я работаю над разработкой приложения UWP, которое будет загружать файл из локальных данных приложения по щелчку Button. Для этого мне нужен объект StorageFolder для Application LocalFolder с использованием метода StorageFolder::GetFolderFromPathAsync(), тогда мне придется использовать метод GetFileAsync() для чтения объекта StorageFile для чтения.

Я написал шаблоны для ожидания ответа от асинхронных методов, таких как GetFolderFromPathAsync(), GetFileAsync(), и т. Д., Прежде чем продолжить.

template <typename T>
T syncAsyncTask(concurrency::task<T> mainTask) {
    std::shared_ptr<std::promise<T>> done = std::make_shared<std::promise<T>>();
    auto future = done->get_future();

    asyncTaskExceptionHandler<T>(mainTask, [&done](bool didFail, T result) {
        done->set_value(didFail ? nullptr : result);
    });
    future.wait();
    return future.get();
}


template <typename T, typename CallbackLambda>
void asyncTaskExceptionHandler(concurrency::task<T> mainTask, CallbackLambda&& onResult) {
    auto t1 = mainTask.then([onResult = std::move(onResult)](concurrency::task<T> t) {
        bool didFail = true;
        T result;
        try {
            result = t.get();
            didFail = false;
        }
        catch (concurrency::task_canceled&) {
            OutputDebugStringA("Win10 call was canceled.");
        }
        catch (Platform::Exception^ e) {
            OutputDebugStringA("Error during a Win10 call:");
        }
        catch (std::exception&) {
            OutputDebugStringA("There was a C++ exception during a Win10 call.");
        }
        catch (...) {
            OutputDebugStringA("There was a generic exception during a Win10 call.");
        }
        onResult(didFail, result);
    });

}

Выпуск:

Когда я вызываю syncAsyncTask() метод с любой задачей, чтобы получить его ответ, он продолжает ждать на future.wait() как mainTask никогда завершено и promise никогда не устанавливает его значение.

см. Ниже код:

void testStorage::MainPage::Btn_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
{
    Windows::Storage::StorageFolder^ localFolder = Windows::Storage::ApplicationData::Current->LocalFolder;
    auto task = concurrency::create_task(Windows::Storage::StorageFolder::GetFolderFromPathAsync(localFolder->Path));
    auto folder = syncAsyncTask<Windows::Storage::StorageFolder^>(task);
    printString(folder->Path);
}

void printString(Platform::String^ text) {
    std::wstring fooW(text->Begin());
    std::string fooA(fooW.begin(), fooW.end());
    const char* charStr = fooA.c_str();
    OutputDebugStringA(charStr);
}

Рабочая среда:

  1. VS2017

  2. Пробовал с C ++ 14 и C ++ 17, столкнулся с той же проблемой.

  3. Windows 10 RS5 Build # 17763

Кто-нибудь когда-нибудь сталкивался с этой проблемой?

Пожалуйста, помогите !! Заранее спасибо.

1 Ответ

0 голосов
/ 05 февраля 2019

Мне удалось взять приведенный выше код и создать простое приложение, воспроизводящее эту проблему. Короче говоря, я смог заставить future.wait() вернуться, сказав продолжение в asyncTaskExceptionHandler для запуска в фоновом потоке:

template <typename T, typename CallbackLambda>
void asyncTaskExceptionHandler(concurrency::task<T> mainTask, CallbackLambda&& onResult) {
    // debug
    printString(mainTask.is_apartment_aware().ToString());

    auto t1 = mainTask.then([onResult = std::move(onResult)](concurrency::task<T> t) {
        bool didFail = true;
        T result;
        try {
            result = t.get();
            didFail = false;
        }
        catch (concurrency::task_canceled&) {
            OutputDebugStringA("Win10 call was canceled.");
        }
        catch (Platform::Exception^ e) {
            OutputDebugStringA("Error during a Win10 call:");
        }
        catch (std::exception&) {
            OutputDebugStringA("There was a C++ exception during a Win10 call.");
        }
        catch (...) {
            OutputDebugStringA("There was a generic exception during a Win10 call.");
        }

    // It works with this
    }, concurrency::task_continuation_context::use_arbitrary());
}

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

  1. В потоке пользовательского интерфейса / STA создайте / обработайте асинхронную операцию с GetFolderFromPathAsync
  2. Передайте эту задачу нашему syncAsyncTask, который, в свою очередь, передает это asyncTaskExceptionHandler.
    • asyncTaskExceptionHandler добавляет продолжение этой задачи, которое планирует его выполнение. По умолчанию задачи выполняются в потоке, который их вызвал. В данном случае это поток UI / STA!
  3. Как только поток запланирован, мы возвращаемся к syncAsyncTask для завершения После нашего вызова asyncTaskExceptionHandler у нас есть future.wait() блоков , пока значение обещания не будет установлено .
    • Это не позволяет нашему потоку пользовательского интерфейса завершить выполнение syncAsyncTask, но также предотвращает запуск нашего продолжения, поскольку он запланирован для запуска в том же потоке, который блокирует!

Другими словами, мы ожидаем в потоке пользовательского интерфейса завершения операции, которая не может начаться до тех пор, пока поток пользовательского интерфейса не завершится, что приведет к нашей тупиковой ситуации.

Используя concurrency::task_continuation_context::use_arbitrary(), мы сообщаем задаче, что можно использовать фоновый поток, если это необходимо (что в данном случае так и есть), и все завершается, как предполагалось.

Документация по этому вопросу, а также пример кода, иллюстрирующего асинхронное поведение, см. В Создание асинхронных операций в C ++ для приложений UWP. .

...