Вы звоните rs.SendMovement
, который возвращает задачу, для которой вы вызываете свойство Result
. Это заблокирует весь поток, живущий в текущем пуле потоков - поскольку пул потоков используется всеми участниками / задачами, использующими его, и обычно он содержит 1 рабочий поток на ядро ЦП. Это означает, что вы фактически отключили все ядро ЦП до тех пор, пока удаленный конец не ответит (или не истечет время ожидания).
В общем случае никогда не следует блокировать поток при работе с асинхронным кодом. В Akka. NET работа с API на основе задач может быть выполнена одним из 2 способов.
Использование ReceiveAsync
Первое решение состоит в том, чтобы ваш актер наследовал от ReceiveActor
и используйте метод ReceiveAsync
для определения вашего обработчика сообщений:
class MyActor : ReceiveActor
{
public MyActor()
{
ReceiveAsync<MyMessage>(async message => {
try
{
var rs = new RemoteServer();
var rsr = await rs.SendMovement(editedMovement, _regionConfig.SiteToken, _userConfig.ServerUrl);
if (rsr != HttpStatusCode.OK)
{
Log("Movement POST failed:" + rsr, LogLevel.Error, "LoggingActor");
}
}
catch (HttpRequestException httpEx)
{
Log("Buffering movement for re-send", LogLevel.Warn, "LoggingActor");
MovementTransmitBuffer.Add(editedMovement);
}
});
}
}
Это создаст что-то, известное как non-reentrant actor - это означает, что пока вы не заблокируете какой-либо нижележащий поток (чтобы он мог использоваться другими субъектами, работающими одновременно), вы БУДЕТЕ заблокировать текущего субъекта, не давая ему получать другие сообщения, пока текущий асинхронный c лямбда-обработчик не достигнет конца.
Использование PipeTo
Другой подход, который по умолчанию используется практически во всей внутренней архитектуре akka, заключается в использовании метода PipeTo
вместо Task:
class MyActor : ActorBase
{
// start with an actor in ready state
protected override bool Receive(object message) => Ready(message);
bool Ready(object message)
{
switch (message)
{
case MyMessage msg:
var rs = new RemoteServer();
rs.SendMovement(editedMovement, _regionConfig.SiteToken, _userConfig.ServerUrl)
.PipeTo(Self,
success: rsr => new Status.Success(rsr),
failure: ex => new Status.Failure(ex));
Become(Waiting);
return true;
default: return false;
}
}
bool Waiting(object message)
{
switch (message)
{
case Status.Success success:
var rsr = (HttpStatusCode)success.Status;
if (rsr != HttpStatusCode.OK)
{
Log("Movement POST failed:" + rsr, LogLevel.Error, "LoggingActor");
}
Become(Ready);
return true;
case Status.Failure failure:
Log("Buffering movement for re-send", LogLevel.Warn, "LoggingActor");
MovementTransmitBuffer.Add(editedMovement);
Become(Ready);
return true;
default: return false;
}
}
}
Таким образом, вы поворачиваете ваш актер в пользовательский конечный автомат. Часть кода, ответственная за ожидание логики c, здесь разделена на два этапа - здесь представлены как Ready
/ Waiting
состояний. После отправки асинхронного запроса c субъект меняет свое поведение - это означает, что теперь он может обрабатывать другой набор входящих сообщений или по-разному реагировать на них. Возвращенное логическое значение сообщает системе субъекта, обработано ли сообщение текущим субъектом или нет - это может вызвать необработанный вызов, который по умолчанию будет записывать необработанное сообщение.
Одним из преимуществ этого подхода является то, что этот субъект имеет значение reentrant - это означает, что, ожидая, пока SendMovement
вернет результат (или потерпит неудачу), этот субъект может свободно принимать и обрабатывать другие сообщения.