Выполнения асинхронной функции в другом потоке

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


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

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

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

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

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


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

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

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

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

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


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

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

это:

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

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

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


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

в этом случае это не похоже на вас действительно хочу Test() метод должен быть асинхронным - по крайней мере, вы не используя тот факт, что он асинхронный. Ты просто начинаешь все по-другому... the 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 вызов, программа продолжается и просто немедленно завершает работу. Это все портит и приводит к неожиданному поведению.