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,

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

Другие вопросы по тегам