Delphi - Making asynchronous calls to WinRT API using TTask.Future

丶灬走出姿态 提交于 2020-01-14 04:23:10

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!