Async и ждать без "потоков"? Могу ли я настроить то, что происходит под капотом?
У меня есть вопрос о том, как настраиваются новые ключевые слова async
/await
и класс Task
в С# 4.5.
Прежде всего, я задумался над моей проблемой: я разрабатываю структуру с помощью следующего дизайна:
- В одном потоке есть список "текущих действий" (обычно от 100 до 200 элементов), которые хранятся в виде собственной структуры данных и хранятся в виде списка. Он имеет функцию
Update()
, которая перечисляет список и проверяет, нужно ли выполнять какие-либо "вещи" и делает это. В основном это похоже на большой планировщик потоков. Чтобы упростить ситуацию, допустим, что "вещи, которые нужно сделать" - это функции, которые возвращают boolean true
, когда они "закончены" (и не должны вызываться следующим Update) и false
, когда диспетчер должен снова вызвать их следующее обновление.
- Все "вещи" не должны запускаться одновременно, а также должны выполняться в этом одном потоке (из-за статических переменных потока)
- Есть и другие темы, которые делают другие вещи. Они структурированы таким же образом: Big loop, который выполняет итерацию нескольких вещей hundret в большой функции
Update()
.
- Темы могут отправлять друг другу сообщения, в том числе "удаленные вызовы процедур". Для этих удаленных вызовов система RPC возвращает какой-то будущий объект в значение результата. В другом потоке вставлена новая "вещь, которую нужно сделать".
- Общей "вещью" является просто последовательность RPC, соединенных вместе. В настоящий момент синтаксис этой "цепочки" очень многословен и сложен, поскольку вам нужно вручную проверить состояние завершения предыдущих RPC и вызвать следующие и т.д.
Пример:
Future f1, f2;
bool SomeThingToDo() // returns true when "finished"
{
if (f1 == null)
f1 = Remote1.CallF1();
else if (f1.IsComplete && f2 == null)
f2 = Remote2.CallF2();
else if (f2 != null && f2.IsComplete)
return true;
return false;
}
Теперь этот звук, похожий на async
и await
на С# 5.0, может помочь мне здесь. Я не на 100% полностью понимаю, что он делает под капотом (какие-либо хорошие ссылки?), Но, как я понял из нескольких разговоров, которые я наблюдал, он точно делает то, что я хочу, с помощью этого красиво простого кода:
async Task SomeThingToDo() // returning task is completed when this is finished.
{
await Remote1.CallF1();
await Remote2.CallF2();
}
Но я не могу найти способ написать мою функцию Update()
, чтобы сделать что-то подобное. async
и await
похоже, хотят использовать класс Task
, который, в свою очередь, кажется, нуждается в реальных потоках?
Мое ближайшее "решение":
Первый поток (который работает SomeThingToDo
) вызывает свои функции только один раз и сохраняет возвращенную задачу и тесты на каждом Update()
, завершена ли задача.
Remote1.CallF1
возвращает новую задачу с пустым значением параметра "Действие как конструктор" и запоминает возвращаемую задачу. Когда F1 фактически завершена, он вызывает RunSynchronously()
в задаче, чтобы пометить ее как завершенную.
Мне кажется, как извращение системы задач. А рядом с ним создается разделяемая память (Task IsComplete
boolean) между двумя потоками, которые я бы хотел заменить нашей удаленной системой обмена сообщениями, если это возможно.
Наконец, это не решает мою проблему, так как она не работает с реализацией, подобной ожиданию SomeThingToDo
выше. Кажется, автоматически созданные объекты задачи, возвращаемые функцией async, немедленно завершаются?
Итак, наконец, мои вопросы:
- Можно ли подключиться к async/await для использования моих собственных реализаций вместо
Task<T>
?
- Если это невозможно, могу ли я использовать
Task
без каких-либо действий, связанных с "блокировкой" и "потоками"?
- Любая хорошая ссылка, что именно происходит, когда я пишу
async
и await
?
Ответы
Ответ 1
Я не на 100% полностью понимаю, что он делает под капотом - любые хорошие ссылки?
Назад, когда мы разрабатывали функцию Mads, Стивен и я написали несколько статей на разных уровнях для журнала MSDN. Ссылки здесь:
http://blogs.msdn.com/b/ericlippert/archive/2011/10/03/async-articles.aspx
Начните с моей статьи, затем Мэдс, затем Стивена.
Кажется, что автоматически сгенерированные объекты задачи, возвращаемые функцией async, сразу завершаются?
Нет, они завершаются, когда код тела метода возвращается или бросается так же, как и любой другой код.
Можно ли подключиться к async/await для использования моих собственных реализаций вместо Task<T>
?
Метод, содержащий await
, должен возвращать void
, Task
или Task<T>
. Однако ожидаемое выражение может возвращать любой тип, если вы можете называть его GetAwaiter()
. Это не должно быть Task
.
Если это невозможно, могу ли я использовать Task без каких-либо действий, связанных с "блокировкой" и "потоками"?
Совершенно верно. A Task
просто представляет работу, которая будет завершена в будущем. Хотя эта работа обычно выполняется в другом потоке, нет необходимости.
Ответ 2
Можно ли подключиться к async/await для использования моих собственных реализаций вместо Task?
Да.
Если это невозможно, могу ли я использовать Task без каких-либо действий, связанных с "блокировкой" и "потоками"?
Да.
Любая хорошая ссылка, что именно происходит, когда я пишу async и жду?
Да.
Я бы отговорил вас от вопросов "да/нет". Вероятно, вы не просто хотите получить ответы "да/нет".
async и ожидают, похоже, хотят использовать класс Task, который, в свою очередь, кажется, нуждается в реальных потоках?
Нет, это не так. A Task
представляет собой нечто, что может быть завершено в какой-то момент в будущем, возможно, с результатом. Это иногда результат некоторых вычислений в другом потоке, но это не обязательно. Это может быть все, что происходит в какой-то момент в будущем. Например, это может быть результатом операции ввода-вывода.
Remote1.CallF1
возвращает новую задачу с пустым значением параметра "Действие как конструктор" и запоминает возвращаемую задачу. Когда F1 действительно завершена, он вызывает RunSynchronously()
в задаче, чтобы пометить ее как завершенную.
Итак, что вам не хватает здесь, это класс TaskCompletionSource
. С этой недостающей частью головоломки много должно укладываться на место. Вы можете создать объект TCS, передать свойство Task
из него Task
, чтобы... кто бы то ни было, а затем использовать свойство SetResult
, чтобы сообщить о его завершении. Это не приведет к созданию каких-либо дополнительных потоков или использованию пула потоков.
Обратите внимание, что если у вас нет результата и вы просто хотите Task
вместо Task<T>
, тогда просто используйте TaskCompletionSource<bool>
или что-то вдоль этих строк, а затем SetResult(false)
или что-то подходящее. Отбрасывая Task<bool>
на Task
, вы можете скрыть эту реализацию из общедоступного API.
Это также должно содержать варианты "Как" первых двух вопросов, которые вы задали, вместо предложенных вами "я-я" версий. Вы можете использовать TaskCompletionSource
для создания задачи, которая будет завершена всякий раз, когда вы говорите, что она используется, используя любую асинхронную конструкцию, которая может включать или не включать дополнительные потоки.
Ответ 3
Чтобы ответить на ваши вопросы:
Можно ли подключиться к async/await для использования моих собственных реализаций вместо Task?
Да. Вы можете ждать чего угодно. Тем не менее, я не рекомендую это.
Если это невозможно, могу ли я использовать Task без каких-либо действий, связанных с "блокировкой" и "потоками"?
Тип Task
представляет будущее. Он не обязательно "запускается" в потоке; он может представлять собой завершение загрузки или истечение таймера и т.д.
Любая хорошая ссылка, что именно происходит, когда я пишу async и жду?
Если вы имеете в виду, что до трансформации кода, этот пост в блоге имеет приятный бок о бок. Он не на 100% точнее в своих деталях, но достаточно написать простой пользовательский awaiter.
Если вы действительно хотите перевернуть async
, чтобы сделать ваши ставки, лучшим вариантом будет Jon Skeet eduasync. Тем не менее, я серьезно не рекомендую вам делать это на производстве.
Вы можете найти мой async
/await
intro полезный как введение в концепцию async
и рекомендуемые способы их использования, Официальная документация MSDN также необычайно хороша.
Я написал AsyncContext
и AsyncContextThread
классы, которые могут работать для вашей ситуации; они определяют однопоточный контекст для методов async
/await
. Вы можете поставить в очередь работу (или отправить сообщения) в AsyncContextThread
, используя ее свойство Factory
.