IScheduler.Schedule против IScheduler.ScheduleAsync?
Интерфейс IScheduler обеспечивает
public static IDisposable Schedule(this IScheduler scheduler, Action action)
а также
public static IDisposable ScheduleAsync(this IScheduler scheduler, Func<IScheduler, CancellationToken, System.Threading.Tasks.Task<IDisposable>> action)
Описание метода для ScheduleAsync:
// Summary:
// Schedules work using an asynchronous method, allowing for cooperative scheduling
// in an imperative coding style.
//
// Parameters:
// scheduler:
// Scheduler to schedule work on.
//
// action:
// Asynchronous method to run the work, using Yield and Sleep operations for
// cooperative scheduling and injection of cancellation points.
//
// Returns:
// Disposable object that allows to cancel outstanding work on cooperative cancellation
// points or through the cancellation token passed to the asynchronous method.
//
// Exceptions:
// System.ArgumentNullException:
// scheduler or action is null.
Может кто-нибудь объяснить разницу между двумя методами?
Когда я должен использовать ScheduleAsync?
и когда я должен использовать расписание?
Что это значит, учитывая совместное планирование в императивном стиле кодирования?
Благодарю.
1 ответ
Вперед
Этот ответ основан на окончательном объяснении непосредственно от команды Rx в этом посте - осторожно, он длинный и охватывает гораздо больше, чем просто этот пункт. Перейдите к разделу " Использование асинхронного" в операторах запросов Rx, и все объяснено, включая конкретный пример ScheduleAsyc
в разделе "Как сделать планировщики более простыми в использовании с помощью await"
Вот моя попытка перефразировать:
Резюме
Основная мотивация для ScheduleAsync
заключается в использовании функции асинхронного ожидания / ожидания в C# 5, чтобы упростить написание кода, который выполняет "честное" планирование многих событий, которые в противном случае могли бы привести к нехватке планировщика других операций. Это то, что подразумевается под "совместным планированием" - хорошо играть с другим кодом, совместно использующим планировщик. Вы делаете это, планируя следующее событие, и затем передавая управление до тех пор, пока это событие не сработает, и подключитесь к этому событию, чтобы запланировать свое следующее событие и так далее.
До Rx 2.0 это было достигнуто с помощью рекурсивного планирования.
Наивный пример
Вот пример из связанной статьи, которая дает реализацию оператора Range. Эта реализация является плохой, потому что она истощает планировщик, не давая контроль:
static IObservable<int> Range(int start, int count, IScheduler scheduler)
{
return Observable.Create<int>(observer =>
{
return scheduler.Schedule(() =>
{
for (int i = 0; i < count; i++)
{
Console.WriteLine("Iteration {0}", i);
observer.OnNext(start + i);
}
observer.OnCompleted();
});
});
}
Обратите внимание на то, как OnNext находится в цикле, забивая планировщик, не давая контроля (особенно плохо, если планировщик однопоточный). Он лишает других операций возможности планировать свои действия и не допускает прерывания в случае отмены. Как мы можем решить это?
Рекурсивное планирование - решение Pre-Rx 2.0
Вот старый способ решения этой проблемы с рекурсивным планированием - довольно сложно увидеть, что происходит. Это не "императивный стиль кодирования". Рекурсивный вызов self()
довольно непроницаемо для мозга, когда вы впервые видите это - и десятый в моем случае, хотя я понял это в конце концов. Этот классический пост легендарного Барта де Смета расскажет вам больше, чем вам когда-либо нужно будет знать об этой технике. Во всяком случае, вот рекурсивный стиль:
static IObservable<int> Range(int start, int count, IScheduler scheduler)
{
return Observable.Create<int>(observer =>
{
return scheduler.Schedule(0, (i, self) =>
{
if (i < count)
{
Console.WriteLine("Iteration {0}", i);
observer.OnNext(start + i);
self(i + 1); /* Here is the recursive call */
}
else
{
observer.OnCompleted();
}
});
});
}
Будучи более справедливым, следующее ожидающее запланированное действие будет отменено, если будет удалена подписка.
Новый стиль Async/await
Вот новый способ с продолжениями посредством преобразования компилятора async/await, который допускает "императивный стиль кодирования". Обратите внимание, что мотивация по сравнению с рекурсивным стилем более четкая - async/await выделяется тем, что происходит, как идиоматично для.NET в целом:
static IObservable<int> Range(int start, int count, IScheduler scheduler)
{
return Observable.Create<int>(observer =>
{
return scheduler.ScheduleAsync(async (ctrl, ct) =>
{
for (int i = 0; i < count; i++)
{
Console.WriteLine("Iteration {0}", i);
observer.OnNext(i);
await ctrl.Yield(); /* Use a task continuation to schedule next event */
}
observer.OnCompleted();
return Disposable.Empty;
});
});
}
Это похоже на цикл, но на самом деле await ctrl.Yield()
собирается дать контроль, позволяющий другому коду попасть в планировщик. Он использует продолжения Задачи только для того, чтобы планировать события по одному, то есть каждая итерация отправляется в планировщик только после завершения предыдущей, избегая длинных очередей непосредственно в планировщике. Отмена также работает, на этот раз среда Rx переводит распоряжение о подписке в токен отмены, переданный через ct
,
Я рекомендую прочитать оригинальное сообщение, от которого я взял это, если ссылка все еще хороша!