Pull to refresh

Эмуляция async/await в Delphi

Reading time 2 min
Views 8.5K
В C# появилась конструкция async/await. Далее показан вариант как добиться подобного поведения на Delphi.

Я предпологаю, что вы знакомы с async/await. Её удобно использовать для операций, где участие процессора не требуется. Процессор только начинает операцию, а из внешнего мира приходит сообщают об её окончании. Хорошим примером может служить вызов на веб сервер. Допустим наш веб сервер умеет выполнять две операции: сложение и умножение.
        async Task<int> MakeAsyncTask_Plus(int aArg1, int aArg2, int aDurationMs)
        {   // эмуляция вызова на веб сервер
            await Task.Delay(aDurationMs);
            return aArg1 + aArg2;
        }
        async Task<int> MakeAsyncTask_Mult(int aArg1, int aArg2, int aDurationMs)
        {   // эмуляция вызова на веб сервер
            await Task.Delay(aDurationMs);
            return aArg1 * aArg2;
        }

Клиент хочет вычислить выражение (1+2)*(3+4). Т.к. результаты сложений независимы, их можно выполнять одновременно:
        async void CalcAsync()
        {
            Task<int> aPlus1 = MakeAsyncTask_Plus(1, 2, 2000);      // 1
            Task<int> aPlus2 = MakeAsyncTask_Plus(3, 4, 2000);      // 2
 
            int aArg1 = await aPlus1;                                // 3
            int aArg2 = await aPlus2;                                // 4
 
            Task<int> aMult = MakeAsyncTask_Mult(aArg1, aArg2, 1000); // 5
            int aRes = await aMult;                                  // 6
            Log(string.Format("{0} * {1} = {2}", aArg1, aArg2, aRes));
        }

До строки "//3"(первого await) метод отрабатывает обычным образом. На await происходит выход из метода, а продолжится (до следующего await) по окончанию операции. Управление передастся механизмом сообщений или в другом потоке — настраивается в окружении. В C# это достигается возможностями компилятора. В Delphi похожего поведения на одном потоке можно достичь при помощи Fiber. Выглядеть подобный метод будет похоже:
procedure TCalcAsync.Execute;
var
  aPlus1: TAsyncTask<Integer>;
  aPlus2: TAsyncTask<Integer>;
  aMult: TAsyncTask<Integer>;
  aArg1, aArg2: Integer;
  aRes: Integer;
begin
  aPlus1 := nil;
  aPlus2 := nil;
  aMult := nil;
  try
    aPlus1 := TAsyncTask_Plus.Create(Self, 1,{+}2, 2000{ms});
    aPlus2 := TAsyncTask_Plus.Create(Self, 3,{+}4, 2000{ms});
 
    aArg1 := aPlus1.Await;
    aArg2 := aPlus2.Await;
 
    aMult := TAsyncTask_Mult.Create(Self, aArg1,{*}aArg2, 1000{ms});
    aRes := aMult.Await;
    Example.Log('%d * %d = %d', [aArg1, aArg2, aRes]);
  finally
    aMult.Free;
    aPlus2.Free;
    aPlus1.Free;
  end;
end;

Пример работает для Delphi 2007, XE2, XE8.
Tags:
Hubs:
+2
Comments 17
Comments Comments 17

Articles