1

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.

Dmitry Streblechenko
  • 62,942
  • 4
  • 53
  • 78
Rohit
  • 909
  • 1
  • 9
  • 20
  • I might be mistaken, but `IAsyncOperation_1__IStorageFile` seems already to represent an asynchronous operation, you are possibly nesting them. Try to omit the `IFutureTask` and use `Completed` event of `IAsyncOperation_1__IStorageFile`. – nil Aug 11 '17 at 12:19

2 Answers2

0

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.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
0

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;
Dmitry Streblechenko
  • 62,942
  • 4
  • 53
  • 78