Вам не нужны дополнительные потоки пула для операций, связанных с вводом-выводом (подробнее об этом здесь). В этом свете ваш рефакторинговый код может выглядеть следующим образом:
async Task AsyncLoop(CancellationToken cancellationToken)
{
using (RedisConnection redis = new RedisConnection("localhost"))
{
var queue = "my_queue";
var reserved = string.Concat(queue, "_reserved");
redis.Open();
while (true)
{
// observe cancellation requests
cancellationToken.ThrowIfCancellationRequested();
var pushRequestTask = redis.Lists.RemoveLastAndAddFirstString(0, queue, reserved);
// continue on a random thread after await,
// thanks to ConfigureAwait(false)
var pushRequest = await pushRequestTask.ConfigureAwait(false);
// process pushRequest
}
}
}
void Loop(CancellationToken cancellationToken)
{
try
{
AsyncLoop().Wait();
}
catch (Exception ex)
{
while (ex is AggregateException && ex.InnerException != null)
ex = ex.InnerException;
// might be: await Console.Error.WriteLineAsync(ex.Message),
// but you cannot use await inside catch, so:
Console.Error.WriteLine(ex.Message);
}
}
Обратите внимание: отсутствие Task.Run
/ Task.Factory.StartNew
не означает, что всегда будет одна и та же цепочка. В этом примере код после await
, скорее всего, будет продолжен в другом потоке из-за ConfigureAwait(false)
. Однако этот ConfigureAwait(false)
может быть избыточным, если в вызывающем потоке нет контекста синхронизации (например, для консольного приложения). Чтобы узнать больше, обратитесь к статьям, перечисленным в async-await Wiki, в частности к "Все дело в контексте синхронизации" Стивена Клири.
Можно воспроизвести однопоточное поведение цикла обработки событий Node.js внутри AsyncLoop
, поэтому все продолжения после await
выполняются в одном и том же потоке. Вы должны использовать настраиваемый планировщик задач (или настраиваемые SynchronizationContext
и TaskScheduler.FromCurrentSynchronizationContext
). Это не сложно реализовать, хотя я не уверен, что это то, о чем вы просите.
В случае, если это приложение на стороне сервера, сериализация продолжений await
в том же потоке в стиле Node.js может повредить масштабируемости приложения (особенно если между awaits
есть какая-то работа, связанная с процессором).
Чтобы ответить на ваши конкретные вопросы:
Следует ли что-либо из того, что у меня есть здесь, быть перемещено из статического основного метода?
Если вы используете цепочку методов async
, в самом верхнем кадре стека будет переход от асинхронного к Task
. В случае с консольным приложением можно блокировать Task.Wait()
внутри Main
, что является самой верхней точкой входа.
Я замечаю, что когда я запускаю этот код, активность моего процессора немного повышается, я полагаю, потому что я создаю несколько продолжений, запрашивающих информацию у Redis. Это нормально или надо смягчить?
Трудно точно сказать, почему повышается активность вашего процессора. Я бы предпочел обвинить серверную часть этого приложения, то, что прослушивает и обрабатывает запросы Redis на localhost
.
Является ли это действительно асинхронным/неблокирующим на данный момент и будет ли код внутри моей внутренней лямбды никогда не связывать мой основной рабочий поток?
Код, который я разместил, действительно неблокирующий, при условии, что new RedisConnection
не блокирует, а RemoveLastAndAddFirstString
не блокирует нигде внутри своей синхронной части.
Создает ли С# дополнительные потоки для продолжений?
Не явно. Поток отсутствует, пока асинхронная операция ввода-вывода находится в процессе выполнения. Однако, когда операция будет завершена, будет поток IOCP из ThreadPool
, назначенный для обработки завершения. Будет ли код после await
продолжаться в этом потоке или в исходном потоке, зависит от контекста синхронизации. Как уже упоминалось, этим поведением можно управлять с помощью ConfigureAwait(false)
.
person
noseratio
schedule
07.03.2014