Подтвердить что ты не робот

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

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

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

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
}
4b9b3361

Ответ 1

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

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

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


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

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

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

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

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


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

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

Что это значит:

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

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

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

Ответ 2

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

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

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

Это не требует какой-либо поддержки async CTP.

Ответ 3

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