问题
In Windows Store Apps, C++(C# is similar though), doing something like
IAsyncAction^ Action = CurrentAppSimulator::ReloadSimulatorAsync(proxyFile);
create_task( Action ).then([this]()
{
}.wait();
results in an unhandled exception. Usually it's
Microsoft C++ exception: Concurrency::invalid_operation at memory location 0x0531eb58
And I kind of need for that action to finish to get my In App Purchase information before trying to use it. The weird thing here is that anything else besides IAsyncAction waits just fine. IAsyncOperation and IAsyncOperationWithProgress worked just fine, but this ? Exception and then crash.
To be honest, I have no idea what's the difference between an IAsyncOperation and an IAsyncAction, they seem similar to me.
UPDATE :
By analyzing this page http://msdn.microsoft.com/en-us/library/vstudio/hh750082.aspx you can figure out that IAsyncAction is just an IAsyncOperation without a return type. But, you can then see that most IAsyncAction-s are waitable. The real problem though is that certain Windows functions just want to execute on a particular thread (for some reason). ReloadSimulatorAsync is one such fine example.
Using code like this :
void WaitForAsync( IAsyncAction ^A )
{
while(A->Status == AsyncStatus::Started)
{
std::chrono::milliseconds milis( 1 );
std::this_thread::sleep_for( milis );
}
AsyncStatus S = A->Status;
}
results in an infinite loop. If called upon other functions it actually works. The problem here is why does a task need to be executed on a particular thread if everything is Async ? Instead of Async it should be RunOn(Main/UI)Thread or similar.
SOLVED, see answer;
回答1:
Calling wait
on the concurrency::task
after you create it completely defeats the point of having tasks in the first place.
What you have to realize is that in the Windows Runtime, there are many asynchronous operations that cannot (or should not) be run on (or waited for on) the UI thread; you've found one of them, and now you're trying to wait on it. Instead of potentially causing a deadlock, you're getting an exception.
To remedy this, you need to use a continuation. You're most of the way there; you're already defining a continuation function:
IAsyncAction^ Action = CurrentAppSimulator::ReloadSimulatorAsync(proxyFile);
create_task( Action ).then([this]()
{
}).wait();
// do important things once the simulator has reloaded
important_things();
...but you're not using it. The purpose of the function you pass into then
is to be called off the UI thread once the task is complete. So, instead, you should do this:
IAsyncAction^ Action = CurrentAppSimulator::ReloadSimulatorAsync(proxyFile);
create_task( Action ).then([this]()
{
// do important things once the simulator has reloaded
important_things();
});
Your important post-reload code won't run until the task is complete, and it will run on a background thread so it doesn't block or deadlock the UI.
回答2:
This is the magical fix that gets the job done :
void WaitForAsync( IAsyncAction ^A )
{
while(A->Status == Windows::Foundation::AsyncStatus::Started)
{
CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);
}
Windows::Foundation::AsyncStatus S = A->Status;
}
回答3:
In general you should use the continuations (.then(...)), like Adam's answer says, and not block. But lets say you want to do a wait for some reason (for testing some code?), you can trigger an event from the last continuation (to use C# parlance):
TEST_METHOD(AsyncOnThreadPoolUsingEvent)
{
std::shared_ptr<Concurrency::event> _completed = std::make_shared<Concurrency::event>();
int i;
auto workItem = ref new WorkItemHandler(
[_completed, &i](Windows::Foundation::IAsyncAction^ workItem)
{
Windows::Storage::StorageFolder^ _picturesLibrary = Windows::Storage::KnownFolders::PicturesLibrary;
Concurrency::task<Windows::Storage::StorageFile^> _getFileObjectTask(_picturesLibrary->GetFileAsync(L"art.bmp"));
auto _task2 = _getFileObjectTask.then([_completed, &i](Windows::Storage::StorageFile^ file)
{
i = 90210;
_completed->set();
});
});
auto asyncAction = ThreadPool::RunAsync(workItem);
_completed->wait();
int j = i;
}
By the way, for some reason this method causes an exception when after it is over in visual studio tests. I've tested it in an app too though and it worked with no problem. I'm not quite sure what the problem is with the test.
If anybody wants a C++/Wrl example then I have that too.
Update 07/08/2017: As requested here is a C++/Wrl example. I have just run this in a Universal Windows (10) Test project in Visual Studio 2017. The key thing here is the weird part Callback<Implements< RuntimeClassFlags<ClassicCom >, IWorkItemHandler , FtmBase >>
, as opposed to just Callback<IWorkItemHandler>
. When I had the latter, the program jammmed except for when it was in a .exe project. I found this solution here: https://social.msdn.microsoft.com/Forums/windowsapps/en-US/ef6f84f6-ad4d-44f0-a107-3922d56662e6/thread-pool-task-blocking-ui-thread . See "agile objects" for more information.
#include "pch.h"
#include "CppUnitTest.h"
#include <Windows.Foundation.h>
#include <wrl\wrappers\corewrappers.h>
#include <wrl\client.h>
#include <wrl/event.h>
#include <memory>
#include "concrt.h"
#include <Windows.System.Threading.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace ABI::Windows::Foundation;
using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;
using namespace Windows::System::Threading;
using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::System::Threading;
namespace TestWinRtAsync10
{
TEST_CLASS(UnitTest1)
{
public:
TEST_METHOD(AsyncOnThreadPoolUsingEvent10Wrl)
{
HRESULT hr = BasicThreadpoolTestWithAgileCallback();
Assert::AreEqual(hr, S_OK);
}
HRESULT BasicThreadpoolTestWithAgileCallback()
{
std::shared_ptr<Concurrency::event> _completed = std::make_shared<Concurrency::event>();
ComPtr<ABI::Windows::System::Threading::IThreadPoolStatics> _threadPool;
HRESULT hr = GetActivationFactory(HStringReference(RuntimeClass_Windows_System_Threading_ThreadPool).Get(), &_threadPool);
ComPtr<IAsyncAction> asyncAction;
hr = _threadPool->RunAsync(Callback<Implements<RuntimeClassFlags<ClassicCom>, IWorkItemHandler, FtmBase>>([&_completed](IAsyncAction* asyncAction) -> HRESULT
{
// Prints a message in debug run of this test
std::ostringstream ss;
ss << "Threadpool work item running.\n";
std::string _string = ss.str();
std::wstring stemp = std::wstring(_string.begin(), _string.end());
OutputDebugString(stemp.c_str());
//
_completed->set();
return S_OK;
}).Get(), &asyncAction);
_completed->wait();
return S_OK;
}
};
}
Update 08/08/2017: More example, per the comments.
#include "pch.h"
#include "CppUnitTest.h"
#include <wrl\wrappers\corewrappers.h>
#include <wrl\client.h>
#include <wrl/event.h>
#include <memory>
#include "concrt.h"
#include <Windows.System.Threading.h>
#include <Windows.ApplicationModel.Core.h>
using namespace ABI::Windows::Foundation;
using namespace Microsoft::WRL;
namespace TestWinRtAsync10
{
TEST_CLASS(TestWinRtAsync_WrlAsyncTesting)
{
public:
TEST_METHOD(PackageClassTest)
{
ComPtr<ABI::Windows::ApplicationModel::IPackageStatics> _pPackageStatics;
HRESULT hr = GetActivationFactory(Microsoft::WRL::Wrappers::HStringReference(RuntimeClass_Windows_ApplicationModel_Package).Get(), &_pPackageStatics);
ComPtr<ABI::Windows::ApplicationModel::IPackage> _pIPackage;
hr = _pPackageStatics->get_Current(&_pIPackage);
ComPtr<ABI::Windows::ApplicationModel::IPackage3> _pIPackage3;
hr = _pIPackage->QueryInterface(__uuidof(ABI::Windows::ApplicationModel::IPackage3), &_pIPackage3);
ComPtr<__FIAsyncOperation_1___FIVectorView_1_Windows__CApplicationModel__CCore__CAppListEntry> _pAsyncOperation;
hr = _pIPackage3->GetAppListEntriesAsync(&_pAsyncOperation);
std::shared_ptr<Concurrency::event> _completed = std::make_shared<Concurrency::event>();
_pAsyncOperation->put_Completed(Microsoft::WRL::Callback<Implements<RuntimeClassFlags<ClassicCom>, ABI::Windows::Foundation::IAsyncOperationCompletedHandler <__FIVectorView_1_Windows__CApplicationModel__CCore__CAppListEntry*>, FtmBase >>
([&_completed](ABI::Windows::Foundation::IAsyncOperation<__FIVectorView_1_Windows__CApplicationModel__CCore__CAppListEntry*>* pHandler, AsyncStatus status)
{
__FIVectorView_1_Windows__CApplicationModel__CCore__CAppListEntry* _pResults = nullptr;
HRESULT hr = pHandler->GetResults(&_pResults);
ComPtr<ABI::Windows::ApplicationModel::Core::IAppListEntry> _pIAppListEntry;
unsigned int _actual;
hr = _pResults->GetMany(0, 1, &_pIAppListEntry, &_actual);
ComPtr<ABI::Windows::ApplicationModel::IAppDisplayInfo> _pDisplayInfo;
hr = _pIAppListEntry->get_DisplayInfo(&_pDisplayInfo);
Microsoft::WRL::Wrappers::HString _HStrDisplayName;
hr = _pDisplayInfo->get_DisplayName(_HStrDisplayName.GetAddressOf());
const wchar_t * _pWchar_displayName = _HStrDisplayName.GetRawBuffer(&_actual);
OutputDebugString(_pWchar_displayName);
_completed->set();
return hr;
}).Get());
_completed->wait();
};
};
}
This outputted:
TestWinRtAsync10
回答4:
Just in case anyone needs here is solution in C++/WinRT. Say you have function ProcessFeedAsync()
that would return IAsyncAction
, just need following simple code:
winrt::init_apartment();
auto processOp{ ProcessFeedAsync() };
// do other work while the feed is being printed.
processOp.get(); // no more work to do; call get() so that we see the printout before the application exits.
source
来源:https://stackoverflow.com/questions/19917466/how-to-wait-for-an-iasyncaction