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.