В C# появилась конструкция async/await. Далее показан вариант как добиться подобного поведения на Delphi.
Я предпологаю, что вы знакомы с async/await. Её удобно использовать для операций, где участие процессора не требуется. Процессор только начинает операцию, а из внешнего мира приходит сообщают об её окончании. Хорошим примером может служить вызов на веб сервер. Допустим наш веб сервер умеет выполнять две операции: сложение и умножение.
Клиент хочет вычислить выражение (1+2)*(3+4). Т.к. результаты сложений независимы, их можно выполнять одновременно:
До строки "//3"(первого await) метод отрабатывает обычным образом. На await происходит выход из метода, а продолжится (до следующего await) по окончанию операции. Управление передастся механизмом сообщений или в другом потоке — настраивается в окружении. В C# это достигается возможностями компилятора. В Delphi похожего поведения на одном потоке можно достичь при помощи Fiber. Выглядеть подобный метод будет похоже:
Пример работает для Delphi 2007, XE2, XE8.
Я предпологаю, что вы знакомы с 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.
Razaz
Странный у вас C# код:)
Расскажите поподробнее про возврат в текущий поток и как передается управление. Как это внутри сделано? Используется IOCP?
leschenko
Код на C# как раз нормальный. А вот на Delphi не сделано ровным счетом ничего. Визуально похоже, но работает совершенно не так.
Razaz
Видимо кого-то задело, что это все в 4 строки пишется, но код и в самом деле выглядит притянутым за уши для аналогии с Delphi. И как раз был вопрос про то как это работает.
leschenko
Так ведь весь прикол в том, что оно не работает. В C# между await'ами UI поток свободен и может выполнять другие задачи, а код Delphi вынес обработку в другой поток, но UI поток занят.
Razaz
Ну дак я и спрашиваю автора дальше об этом:
Все это выглядит как Task.Result, но были упомянуты какие-то сообщения..
lair
Ну вообще,
SynchronizationContext.Post
— это вполне себе сообщение, а именно так работает постановка continuation на определенный контекст (по умолчанию вawait
это включено).Razaz
Ну и хотелось бы узнать как точно то работает на Delphi :)
lair
Из вашего коммента не очевидно, к какому коду был вопрос. Судя по количеству ответов — не мне одному.
Razaz
"Расскажите поподробнее про возврат в текущий поток и как передается управление. Как это внутри сделано? Используется IOCP?"
lair
… и все это под C#-ным кодом. Вот вам и рассказывают про C#-ный код.
Razaz
Я просто отформатировал код автора, что бы не пугало людей, которые с C# не сильно знакомы :) Извиняюсь ели ввел в заблуждение.
SuvAlexander
1) Покажите, пожалуйста, строчку кода, которая выполняется не в главном потоке.
2) Откуда информация, что главный поток занят? Что вам мешает повторно нажать на кнопочку "Async" во время "вычислений"?
Добавил логирование сообщения на выход из обработчика нажатия:
procedure TExample.btStartAsyncClick(Sender: TObject);
begin
// mmLog.Clear;
TCalcAsync.Create.Switch;
Log('btStartAsyncClick finish', []);
end;
Дважды быстро нажал на "Async". Получил лог (обратите внимание на положение этого сообщения — оно между сообщениями из TCalcAsync.Execute):
1 + 2 start
3 + 4 start
btStartAsyncClick finish
1 + 2 start
3 + 4 start
btStartAsyncClick finish
1 + 2 = 3
3 + 4 = 7
3 7 start
1 + 2 = 3
3 + 4 = 7
3 7 start
3 7 = 21
3 7 = 21
leschenko
Извиняюсь. Не туда посмотрел. Действительно, UI не занят.
Но все равно так писать — извражение.
lair
Вы про свой код или про C#?
mayorovp
Оператор await работает следующим образом:
Реально там все еще сложнее — главным образом, из-за необходимости повторно выбрасывать исключения с сохранением стектрейса.
Большую часть реализации можно увидеть начиная отсюда: http://referencesource.microsoft.com/#mscorlib/system/runtime/compilerservices/AsyncMethodBuilder.cs,108
Также реализация await упрощенна расписана в комментарии к файлу http://referencesource.microsoft.com/#mscorlib/system/runtime/compilerservices/TaskAwaiter.cs
Магия шагов 1 и 8 (возобновление метода) реализуется компилятором.
Razaz
Зачем вы мне про C# И .Net? Это я и так знаю. Вопрос был к автору — как именно это реализовано на Delphi.
leschenko
Никак. Это не реализовано.