问题
Related to this question, I am trying to implement a procedure that uses the WinRT API to set the desktop wallpaper. To mimic the await
functionality in C#, I am using TTask.Future
(link) as outlined here and here.
My implementation looks like this:
class procedure TUtilityWin.SetWallpaper(AFileName: String);
var
lStorageFile: IStorageFile;
liao_storagefile: IAsyncOperation_1__IStorageFile;
lFutureTask: IFuture<IAsyncOperation_1__IStorageFile>;
begin
//WinRT Implementation
if TUserProfile_UserProfilePersonalizationSettings.IsSupported then
begin
lFutureTask:=TTask.Future<IAsyncOperation_1__IStorageFile>(
function: IAsyncOperation_1__IStorageFile
begin
Result:=TStorageFile.GetFileFromPathAsync(HSTRING(AFileName));
end);
liao_storagefile:=lFutureTask.Value;
lStorageFile:=liao_storagefile.GetResults;
TUserProfile_UserProfilePersonalizationSettings.Current.TrySetWallpaperImageAsync(lStorageFile);
end;
end;
Per my understanding, when I try to get lFutureTask.Value
, the application suspends the current thread until lFutureTask is completed (if it is not already) and then provides the value. However, when I run the application, I get the error message: EOleException with message 'A method was called at an unexpected time'
. The break is on this line: lStorageFile:=liao_storagefile.GetResults;
I am new to TTask as well as the WinRT API - so I am certain I am missing something very basic here. Would appreciate any pointers on what would be causing this or what I could be doing differently to fix this. Thanks in advance.
回答1:
I took a look at the Delphi docs linked in your question, and AFAICT both ITask
and IFuture
only represent methods that execute on a separate thread (what I call "Delegate Tasks"). There doesn't appear to be any support for asynchronous tasks (what I call "Promise Tasks"). IAsyncOperation<T>
is a WinRT type representing an asynchronous task, hence the problems you're having. In particular, there doesn't appear to be any Delphi support for registering continuations onto their "task"s.
So, unless there's some Delphi support for a non-threaded Future/Promise, you're going to have to block a thread.
Currently, your code is spinning up a threaded task that only starts the asynchronous operation (GetFileFromPathAsync
). The threaded task is not waiting for the asynchronous operation to complete (IAsyncOperation<T>.Completed
), so that task completes immediately after starting the operation, and then the outer code calls GetResult
when the operation doesn't have a result yet, causing the exception.
So, to fix this, you'll need a way to have the threaded task block until the asynchronous operation completes. Since WinRT types are purely asynchronous (with no support for synchrony), and since the Delphi types are purely synchronous (with no support for asynchrony), you'll have to bridge it yourself. The best solution is probably the Delphi equivalent of a ManualResetEvent.
Something like this should work:
class procedure TUtilityWin.SetWallpaper(AFileName: String);
var
lStorageFile: IStorageFile;
lFutureTask: IFuture<IStorageFile>;
begin
if TUserProfile_UserProfilePersonalizationSettings.IsSupported then
begin
lFutureTask:=TTask.Future<IStorageFile>(
function: IStorageFile
var
liao_storagefile: IAsyncOperation_1__IStorageFile;
mre: ManualResetEvent;
result: IStorageFile;
begin
mre:= // Create ManualResetEvent
liao_storagefile:=TStorageFile.GetFileFromPathAsync(HSTRING(AFileName));
liao_storagefile.Completed... // Add handler that will set `result` and then set the ManualResetEvent
mre.Wait(); // Wait for the ManualResetEvent to be set
Result:=result;
end);
liao_storagefile:=lFutureTask.Value;
lStorageFile:=liao_storagefile.GetResults;
TUserProfile_UserProfilePersonalizationSettings.Current.TrySetWallpaperImageAsync(lStorageFile);
end;
end;
Sorry, I don't know Delphi, but hopefully this will give you a general direction.
回答2:
The following (handling an asynchronous call to WebAuthenticationCoreManager.FindAccountProviderAsync
) works for me, even though it is as ugly as it gets compared to async/await
in .Net:
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Winapi.Winrt,
System.Win.WinRT,
Winapi.Security.Credentials,
Winapi.Security,
Winapi.Foundation.Types
...
type
TCompleteOperationCompleted_IWebAccountProvider = reference to procedure (Value : IWebAccountProvider);
TCompleteOperationError_IWebAccountProvider = reference to procedure ();
TAsyncOperationCompletedHandler_1__IWebAccountProvider = class (TInterfacedObject,
AsyncOperationCompletedHandler_1__IWebAccountProvider_Delegate_Base,
AsyncOperationCompletedHandler_1__IWebAccountProvider)
private
fOnCompleted : TCompleteOperationCompleted_IWebAccountProvider;
fOnError : TCompleteOperationError_IWebAccountProvider;
protected
procedure Invoke(asyncInfo: IAsyncOperation_1__IWebAccountProvider; asyncStatus: AsyncStatus); safecall;
public
constructor Create(OnCompleted : TCompleteOperationCompleted_IWebAccountProvider;
OnError : TCompleteOperationError_IWebAccountProvider = nil);
end;
constructor TAsyncOperationCompletedHandler_1__IWebAccountProvider.Create(
OnCompleted: TCompleteOperationCompleted_IWebAccountProvider;
OnError: TCompleteOperationError_IWebAccountProvider);
begin
fOnCompleted := OnCompleted;
fOnError := OnError;
end;
procedure TAsyncOperationCompletedHandler_1__IWebAccountProvider.Invoke(
asyncInfo: IAsyncOperation_1__IWebAccountProvider; asyncStatus: AsyncStatus);
begin
case asyncStatus of
Winapi.Foundation.Types.AsyncStatus.Completed : if Assigned(fOnCompleted) then fOnCompleted(asyncInfo.GetResults());
Winapi.Foundation.Types.AsyncStatus.Error : if Assigned(fOnError) then fOnError();
else ;//todo
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
const
DefaultProviderId = 'https://login.windows.local';
MicrosoftProviderId = 'https://login.microsoft.com';
var
account: IAsyncOperation_1__IWebAccountProvider;
webAccount: IWebAccountProvider;
begin
account := TAuthentication_Web_Core_WebAuthenticationCoreManager.FindAccountProviderAsync(TWindowsString.Create(MicrosoftProviderId{DefaultProviderId}));
account.Completed := TAsyncOperationCompletedHandler_1__IWebAccountProvider.Create(
procedure(Value : IWebAccountProvider)
begin
ShowMessage('Async operation completed');
//todo
end,
nil
);
end;
来源:https://stackoverflow.com/questions/45633237/delphi-making-asynchronous-calls-to-winrt-api-using-ttask-future