Delphi - Выполнение асинхронных вызовов WinRT API с использованием TTask.Future
В связи с этим вопросом я пытаюсь реализовать процедуру, которая использует WinRT API для установки обоев рабочего стола. Подражать await
функциональность в C#, я использую TTask.Future
( ссылка), как указано здесь и здесь.
Моя реализация выглядит так:
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;
В моем понимании, когда я пытаюсь получить lFutureTask.Value
приложение приостанавливает текущий поток до тех пор, пока lFutureTask не будет завершен (если это еще не сделано), а затем предоставит значение. Однако, когда я запускаю приложение, я получаю сообщение об ошибке: EOleException with message 'A method was called at an unexpected time'
, Разрыв на этой линии: lStorageFile:=liao_storagefile.GetResults;
Я новичок в TTask, а также в WinRT API - так что я уверен, что мне здесь чего-то не хватает. Буду признателен за любые указания на то, что может вызвать это или что я мог бы сделать по-другому, чтобы исправить это. Заранее спасибо.
1 ответ
Следующее (обработка асинхронного вызова WebAuthenticationCoreManager.FindAccountProviderAsync
) работает для меня, хотя он настолько уродлив, насколько это возможно по сравнению с async/await
в.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;
Я взглянул на документы Delphi, связанные в вашем вопросе, и AFAICT оба ITask
а также IFuture
представляют только те методы, которые выполняются в отдельном потоке (то, что я называю "делегировать задачи"). Кажется, что нет никакой поддержки для асинхронных задач (то, что я называю "Задачи обещания"). IAsyncOperation<T>
является типом WinRT, представляющим асинхронную задачу, поэтому у вас возникли проблемы В частности, похоже, что Delphi не поддерживает регистрацию продолжений в их "заданиях".
Таким образом, если только у Delphi нет поддержки для многопоточного Future / Promise, вам придется блокировать поток.
В настоящее время ваш код раскручивает многопоточную задачу, которая только запускает асинхронную операцию (GetFileFromPathAsync
). Потоковая задача не ожидает завершения асинхронной операции (IAsyncOperation<T>.Completed
), так что задача завершается сразу после начала операции, а затем вызывается внешний код GetResult
когда операция еще не имеет результата, вызывая исключение.
Итак, чтобы это исправить, вам понадобится способ иметь блок задач, пока асинхронная операция не завершится. Поскольку типы WinRT являются чисто асинхронными (без поддержки синхронности), а типы Delphi являются чисто синхронными (без поддержки асинхронности), вам придется соединять их самостоятельно. Наилучшим решением, вероятно, является Delphi-эквивалент ManualResetEvent.
Примерно так должно работать:
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;
Извините, я не знаю Delphi, но, надеюсь, это даст вам общее направление.