TaskEx.Yield (TaskScheduler) - PullRequest
       29

TaskEx.Yield (TaskScheduler)

3 голосов
/ 05 января 2012

В прошлом месяце я задал следующий вопрос, в результате которого я узнал TaskEx.Yield:

Могут ли асинхронные методы иметь дорогой код до первого 'ожидания'?

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

Было бы замечательно что-то вроде следующего:

public static YieldAwaitable Yield(TaskScheduler taskScheduler)
{
    return new YieldAwaitable(taskScheduler);
}

Однако текущая реализация Async CTP предлагает только:

public static YieldAwaitable Yield()
{
    return new YieldAwaitable(SynchronizationContext.Current ?? TaskScheduler.Current);
}

Будет ли следующее предложение приемлемо эффективной альтернативой?

await Task.Factory.StartNew(() => { }, CancellationToken.None, TaskCreationOptions.None, this.TaskScheduler);

Ответы [ 2 ]

5 голосов
/ 06 января 2012

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

Поддержка асинхронного языка основана на неявном контексте планирования.Я не вижу необходимости внедрения зависимости здесь.Любой метод, вызывающий ваш метод async, может при необходимости предоставить свой собственный контекст.

Ваш вариант:

await Task.Factory.StartNew(() => { }, CancellationToken.None, TaskCreationOptions.None, this.TaskScheduler);

не будет работать должным образом.Это поставит namb lambda в очередь на определенный TaskScheduler, а затем возобновит метод в неявном контексте планирования.

Более ранняя версия Async CTP действительно обеспечивала метод «возврата к другому контексту», называемый SwitchTo.Он был удален , потому что его слишком легко неправильно использовать.

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

PS Не составляет особого труда создать и установить собственный контекст, например, для целей тестирования.Я написал AsyncContext как простой контекст планирования для модульного тестирования и консольных программ.Async CTP поставляется с GeneralThreadAffineContext, WindowsFormsContext и WpfContext для тестирования.Любой из них может быть установлен с помощью SynchronizationContext.SetSynchronizationContext.ИМО, DI излишне.

2 голосов
/ 06 января 2012

Языковая поддержка async позволяет ожидающим управлять собственным расписанием.Существует планирование по умолчанию, которое срабатывает, когда вы ожидаете Задачу, но есть много способов изменить поведение по умолчанию, добавив немного больше кода.

В частности, await предназначен для выполнения следующих действий:

  1. Проверка, чтобы увидеть, выполнено ли ожидаемое (GetAwaiter (). IsCompleted).
  2. Если (и только если) ожидаемое не было выполнено, попросите его запланировать остальныеметода (GetAwaiter (). OnCompleted (...))
  3. «Осуществить» ожидаемый результат.Это означает, что нужно либо вернуть возвращаемое значение, либо убедиться, что возникшие исключения снова возникли.

Так что имейте в виду, что если ожидаемое заявило, что оно «выполнено», то ничего не запланируется.

Task.Yield () является новым, поскольку он возвращает ожидаемое, которое никогда выполнено, с явной целью дать вам способ явно прекратить выполнение на данный момент и немедленнозапланировать все остальное для исполнения.Он использует окружающий контекст, но есть много других способов сделать то же самое без окружающего контекста.

Примером переопределения поведения по умолчанию является случай, когда вы ожидаете незавершенную задачу, но используете ConfigureAwait (false) метод.ConfigureAwait (false) оборачивает задачу в специальное ожидание, что всегда использует планировщик задач по умолчанию, эффективно всегда возобновляя работу в пуле потоков.«Ложь» предназначена для явного игнорирования окружающего контекста синхронизации.

Не существует Task.Yield (). ConfigureAwait (false), но рассмотрим следующую гипотетическую:

// ... A ...
await Task.Yield().ConfigureAwait(false);
// ... B ...

Вышесказанное может быть в значительной степени достигнуто с помощью

// ... A ...
await Task.Run(() => {
    // ... B ...
});

Там есть немного больше ясности и вложенности, но это не обязательно плохо, учитывая то, что происходит.Часть 'A' всегда выполняется в вызывающем потоке, тогда как часть 'B' всегда выполняется в пуле потоков.Существуют определенные различия в том, как вы должны смотреть на код, который находится в разделах A и B, поэтому, таким образом, наличие нескольких кратких переходов между ними должно заставить людей сделать паузу, прежде чем предположить, что оба раздела имеют одинаковый контекст.

...