IScheduler.Schedule vs IScheduler.ScheduleAsync?

帅比萌擦擦* 提交于 2019-12-24 12:11:19

问题


The IScheduler interface provides

public static IDisposable Schedule(this IScheduler scheduler, Action action)

and

public static IDisposable ScheduleAsync(this IScheduler scheduler, Func<IScheduler, CancellationToken, System.Threading.Tasks.Task<IDisposable>> action)

The method description for 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.

Can someone explain the differences between the 2 methods?

When should i use ScheduleAsync?

and when should i use Schedule?

What does it means by allowing for cooperative scheduling in an imperative coding style?

Thanks.


回答1:


Forward

This answer is based on the definitive explanation direct from the Rx team in this post - caution it's long and covers much more than just this point. Go down to the section entitled Leveraging “async” in Rx query operators and all is explained, including a specific example on ScheduleAsyc in the section titled Making schedulers easier to use with “await”

Here is my attempt to paraphrase:

Summary

The primary motivation for ScheduleAsync is to embrace the async/await feature of C# 5 to simplify writing code that performs "fair" scheduling of many events that might otherwise cause scheduler starvation of other operations. This is what is meant by "cooperative scheduling" - playing nice with other code sharing the scheduler. You do this by scheduling the next event, and then yielding control until that event fires and hooking in to that event to schedule your next event and so on.

Prior to Rx 2.0 this was achieved via recursive scheduling.

Naïve Example

Here is the example from the linked article that gives an implementation of the Range operator. This implementation is poor because it starves the scheduler by not yielding control:

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();
        });
    });
}

Notice how the OnNext sits in a loop hammering the scheduler without yielding control (particularly bad if the scheduler is single threaded). It starves other operations of the chance to Schedule their actions, and it doesn't allow for aborting in the event of cancellation. How can we solve this?

Recursive Scheduling - The Pre-Rx 2.0 Solution

Here is the old way this was tackled with recursive scheduling - it's quite hard to see what's going on. It's not an "imperative coding style". The recursive call self() is pretty brain-meltingly opaque the first time you see it - and the tenth in my case, although I got it eventually. This classic post by the legendary Bart de Smet will tell you more than you'll ever need to know about this technique. Anyway, here's the recursive style:

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();
            }
        });
    });
}

As well as being fairer, the next pending scheduled action will be canceled if the subscription is disposed.

The new Async/await Style

Here is the new way with continuations via compiler transformation of async/await which allows the "imperative coding style". Note that the motivation compared with the recursive style is greater legibility - the async/await stand out showing what's happening in a way idiomatic to .NET in general:

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;
        });
    });
}

It looks just like a for-loop, but in reality the await ctrl.Yield() is going to yield control allowing other code to get at the scheduler. It uses Task continuations to only schedule events one at a time - that is each iteration is only posted to the scheduler when the previous one is done, avoiding long queues directly on the scheduler. Cancelation also works, this time the Rx framework translates the disposal of the subscription on to a cancellation token passed in via ct.

I recommend reading the original post I took this from if the link is still good!



来源:https://stackoverflow.com/questions/19489004/ischeduler-schedule-vs-ischeduler-scheduleasync

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!