Запустить асинхронную функцию в другом потоке

Я оцениваю 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 ответа

Решение

Я новичок (мой девственный пост) в 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#, чтобы с Async Lambdas получилось правильное - что может вернуть void, Task, или же Task<T>,

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

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


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

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

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

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

Task реализация await очень разумна и относится к текущему потоку 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#, я бы хотел их услышать:)

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

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

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

Это не требует никаких асинхронных CTP совершенства.

Было бы, если бы это не было консольное приложение. Например, если вы делаете это в приложении 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 вызов, программа продолжается, и сразу же выходит. Это имеет тенденцию портить все и приводить к неожиданному поведению.

Другие вопросы по тегам