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


Я оцениваю асинхронный 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 14

3 ответа:

Я новичок (мой девственный пост) в Stack Overflow, но я в восторге, что вы спрашиваете об асинхронном 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
}

Задача.Запуск против задачи.RunEx

, поскольку это ОСАГО устанавливается на верхней части .Net версии 4.0, мы не хотели, чтобы залатать фактическое System.Threading.Tasks.Task введите mscorlib. Вместо этого API playground называются FooEx, когда они конфликтуют.

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

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

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


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

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

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

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

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


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

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

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

  1. в окружающем потоке синхронно вызовите TaskEx.Бежать(...) для выполнения лямбды в пуле потоков.
  2. A поток пула потоков предназначен для лямбда-кода, и он вызывает ваш асинхронный метод.
  3. асинхронный метод Test () вызывается из лямбды. Поскольку лямбда-код выполнялся в пуле потоков, любые продолжения внутри Test() могут выполняться в любом потоке в пуле потоков.
  4. лямбда на самом деле не освобождает стек этого потока, потому что в нем не было ожиданий. Поведение TPL в этом случае зависит от того, был ли тест() фактически завершен до вызова Wait (). Тем не менее, в этом случае есть реальная возможность того, что вы будете блокировать поток пула потоков, пока он ожидает завершения выполнения функции Test() в другом потоке.

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

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

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

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

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

Это не так. требует асинхронного ОСАГО добра.

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