Запустить асинхронную функцию в другом потоке - PullRequest
17 голосов
/ 11 февраля 2011

Я оцениваю Async CTP.

Как мне начать выполнение асинхронной функции в потоке другого пула потоков?

static async Task Test()
{
    // Do something, await something
}

static void Main( string[] args )
{
    // Is there more elegant way to write the line below?
    var t = TaskEx.Run( () => Test().Wait() );

    // Doing much more in this same thread
    t.Wait(); // Waiting for much more then just this single task, this is just an example
}

Ответы [ 3 ]

52 голосов
/ 12 февраля 2011

Я новичок (мой девственный пост) в Stack Overflow, но я потрясен, что вы спрашиваете об Async CTP, так как я в команде, работающей над этим в Microsoft:)

Мне кажется, я понимаю, к чему вы стремитесь, и есть пара вещей, которые вы делаете правильно, чтобы вы там оказались.

То, что я думаю, вы хотите:

static async Task Test()
{
    // Do something, await something
}

static void Main(string[] args)
{
    // In the CTP, use Task.RunEx(...) to run an Async Method or Async Lambda
    // on the .NET thread pool
    var t = TaskEx.RunEx(Test);
    // the above was just shorthand for
    var t = TaskEx.RunEx(new Func<Task>(Test));
    // because the C# auto-wraps methods into delegates for you.

    // Doing much more in this same thread
    t.Wait(); // Waiting for much more then just this single task, this is just an example
}

Task.Run vs. Task.RunEx

Поскольку этот CTP устанавливается поверх .NET 4.0, мы не хотели исправлять фактический System.Threading.Tasks.Task тип в mscorlib. Вместо этого API игровой площадки называются FooEx, когда они конфликтуют.

Почему мы назвали некоторых из них Run(...), а некоторые из RunEx(...)? Причина в том, что произошла редизайн перегрузки методов, которую мы еще не завершили к моменту выпуска CTP. В нашей текущей рабочей кодовой базе нам фактически пришлось немного подправить правила перегрузки метода C #, чтобы с асинхронными лямбда-кодами произошла правильная ситуация, которая может возвращать void, Task или Task<T>.

Проблема в том, что когда асинхронный метод или лямбда-выражения возвращают Task или Task<T>, они фактически не имеют внешнего типа задачи в выражении возврата, потому что задача генерируется для вас автоматически как часть метода или лямбда-вызов. Нам это кажется очень подходящим для ясности кода, хотя раньше это было совершенно иначе, поскольку обычно выражение выражений return напрямую конвертируется в тип возврата метода или лямбды.

Таким образом, и async void lambdas, и async Task lambdas поддерживают return; без аргументов. Отсюда необходимость уточнения в разрешении перегрузки метода, чтобы решить, какой из них выбрать. Таким образом, единственная причина, по которой у вас есть Run (...) и RunEx (...), заключается в том, что к моменту выхода PDC 2010 мы обеспечим более качественную поддержку других частей Async CTP.


Как думать об асинхронных методах / лямбдах

Я не уверен, что это может привести к путанице, но я подумал, что упомяну об этом - когда вы пишете асинхронный метод или асинхронную лямбду, он может принимать определенные характеристики того, кто его вызывает. Это основано на двух вещах:

  • Тип, который вы ожидаете
  • И, возможно, контекст синхронизации (в зависимости от выше)

Дизайн CTP для await и наш текущий внутренний дизайн основаны на шаблонах, поэтому провайдеры API могут помочь в разработке яркого набора вещей, от которых вы можете «ждать». Это может варьироваться в зависимости от типа, который вы ожидаете, и общий тип для этого Task.

Реализация ожидания

Task очень разумна, и от нее зависит SynchronizationContext текущего потока, чтобы решить, как отложить работу. В случае, если вы уже находитесь в цикле сообщений WinForms или WPF, ваше отложенное выполнение вернется в тот же цикл обработки сообщений (как если бы вы использовали BeginInvoke() «остаток вашего метода»). Если вы ожидаете Задачу и уже находитесь в пуле потоков .NET, то «остаток вашего метода» возобновится в одном из потоков пула потоков (но не обязательно точно в том же), так как они были объединены для начала и Скорее всего, вы счастливы использовать первый доступный поток пула.


Предупреждение об использовании методов Wait ()

В вашем примере вы использовали: var t = TaskEx.Run( () => Test().Wait() );

Что это делает:

  1. В окружающем потоке синхронно вызывается TaskEx.Run (...) для выполнения лямбды в пуле потоков.
  2. Поток пула потоков предназначен для лямбды и вызывает ваш асинхронный метод.
  3. Асинхронный метод Test () вызывается из лямбды. Поскольку лямбда выполнялась в пуле потоков, любые продолжения внутри Test () могут выполняться в любом потоке в пуле потоков.
  4. Лямбда на самом деле не освобождает стек этого потока, потому что его там не было.Поведение TPL в этом случае зависит от того, действительно ли Test () завершился до вызова Wait ().Однако в этом случае существует реальная возможность того, что вы будете блокировать поток пула потоков, пока он ожидает завершения выполнения Test () в другом потоке.

Это основное преимущество 'Оператор await 'заключается в том, что он позволяет добавлять код, который выполняется позже, но без блокировки исходного потока.В случае пула потоков вы можете добиться лучшего использования потоков.

Дайте мне знать, если у вас есть другие вопросы об Async CTP для VB или C #, я бы хотел их услышать:)

5 голосов
/ 11 февраля 2011

Обычно это метод, возвращающий Task, чтобы определить, где он работает, если он начинает действительно новую работу, вместо того, чтобы просто делать что-то еще.

В этом случае не похоже, что вы действительно хотите, чтобы метод Test() был асинхронным - по крайней мере, вы не используете факт, что он асинхронный. Вы просто запускаете материал в другом потоке ... метод Test() может быть полностью синхронным, и вы можете просто использовать:

Task task = TaskEx.Run(Test);
// Do stuff
t.Wait();

Для этого не требуется никаких асинхронных CTP-функций.

2 голосов
/ 11 февраля 2011

Было бы, если бы это не было консольное приложение.Например, если вы делаете это в приложении Windows Forms, вы можете сделать следующее:

// Added to a button click event, for example
public async void button1_Click(object sender, EventArgs e)
{
    // Do some stuff
    await Test();
    // Do some more stuff
}

Однако в консоли нет значения по умолчанию SynchronizationContext, так что это не будет работать так, как выожидать.В консольном приложении вам нужно явно захватить задачу, а затем ждать в конце.

Если вы делаете это в потоке пользовательского интерфейса в Windows Forms, WPF или даже в службе WCF, будетдопустимый SynchronizationContext, который будет использоваться для правильного распределения результатов.Однако в консольном приложении, когда элемент управления «возвращается» при вызове await, программа продолжается и сразу же завершает работу.Это все портит и приводит к неожиданному поведению.

...