Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TPL FromAsync with TaskScheduler and TaskFactory

I am trying to create a Task pipeline/ordered scheduler in combination with using TaskFactory.FromAsync.

I want to be able to fire off web service requests (using FromAsync to use I/O completion ports) but maintain their order and only have a single one executing at any one time.

At the moment I do not use FromAsync so I can do TaskFactory.StartNew(()=>api.DoSyncWebServiceCall()) and rely on the OrderedTaskScheduler used by the TaskFactory to ensure that only one request is outstanding.

I assumed that this behaviour would stay when using the FromAsync method but it does not:

TaskFactory<Stuff> taskFactory = new TaskFactory<Stuff>(new OrderedTaskScheduler());
var t1 = taskFactory.FromAsync((a, s) => api.beginGetStuff(a, s), a => api.endGetStuff(a));
var t2 = taskFactory.FromAsync((a, s) => api.beginGetStuff(a, s), a => api.endGetStuff(a));
var t3 = taskFactory.FromAsync((a, s) => api.beginGetStuff(a, s), a => api.endGetStuff(a));

All of these beginGetStuff methods get called within the FromAsync call (so although they are dispatched in order but there are n api calls occurring at the same time).

There is an overload of FromAsync that takes a TaskScheduler:

public Task FromAsync(
    IAsyncResult asyncResult,
    Action<IAsyncResult> endMethod,
    TaskCreationOptions creationOptions,
    TaskScheduler scheduler
)

but the docs say:

The TaskScheduler that is used to schedule the task that executes the end method.

And as you can see, it takes the already constructed IAsyncResult, not a Func<IAsyncResult>.

Does this call for a custom FromAsync method or am I missing something? Can anyone suggest where to start on this implementation?

Cheers,

EDIT:

I want to abstract this behaviour away from the caller so, as per the behaviour of TaskFactory (with a specialized TaskScheduler), I need the Task to be returned immediately - this Task will not only encapsulate the FromAsync Task but also the queueing of that task whilst it awaits its turn to execute.

One possible solution:

class TaskExecutionQueue
{
    private readonly OrderedTaskScheduler _orderedTaskScheduler;
    private readonly TaskFactory _taskFactory;
    public TaskExecutionQueue(OrderedTaskScheduler orderedTaskScheduler)
    {
        _orderedTaskScheduler = orderedTaskScheduler;
        _taskFactory = new TaskFactory(orderedTaskScheduler);

    }

    public Task<TResult> QueueTask<TResult>(Func<Task<TResult>> taskGenerator)
    {
        return _taskFactory.StartNew(taskGenerator).Unwrap();
    }
}

However, this utilizes a thread whilst the FromAsync call is occurring. Ideally I'd not have to do that.

like image 432
jamespconnor Avatar asked Oct 05 '22 18:10

jamespconnor


1 Answers

You cannot schedule IO tasks because they do not have a thread associated with them. The Windows Kernel provides thread-less IO operations. Starting these IOs does not involve managed code and the TaskScheduler class does not come into play.

So you have to delay starting the IO until you are sure you actually want the network to be hit. You could use SemaphoreSlim.WaitAsync to throttle the amount of tasks currently running. Await the result of that method before starting the individual IO and awaiting that as well.

like image 98
usr Avatar answered Oct 10 '22 00:10

usr