繁体   English   中英

运行任务 <T> 在自定义调度程序上

[英]Running Task<T> on a custom scheduler

我正在创建一个通用的帮助程序类,该类将帮助确定对API的请求的优先级,同时限制它们发生的并行化。

考虑以下应用程序的关键方法;

    public IQueuedTaskHandle<TResponse> InvokeRequest<TResponse>(Func<TClient, Task<TResponse>> invocation, QueuedClientPriority priority, CancellationToken ct) where TResponse : IServiceResponse
    {
        var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
        _logger.Debug("Queueing task.");
        var taskToQueue = Task.Factory.StartNew(async () =>
        {
            _logger.Debug("Starting  request {0}", Task.CurrentId);
            return await invocation(_client);
        }, cts.Token, TaskCreationOptions.None, _schedulers[priority]).Unwrap();
        taskToQueue.ContinueWith(task => _logger.Debug("Finished task {0}", task.Id), cts.Token);
        return new EcosystemQueuedTaskHandle<TResponse>(cts, priority, taskToQueue);
    }

无需过多讨论,我想在出现队列时Task<TResponse>> invocationTask<TResponse>> invocation返回的Task<TResponse>> invocation 我正在使用由唯一枚举索引的QueuedTaskScheduler构造的队列的集合;

        _queuedTaskScheduler = new QueuedTaskScheduler(TaskScheduler.Default, 3);
        _schedulers = new Dictionary<QueuedClientPriority, TaskScheduler>();
        //Enumerate the priorities
        foreach (var priority in Enum.GetValues(typeof(QueuedClientPriority)))
        {
            _schedulers.Add((QueuedClientPriority)priority, _queuedTaskScheduler.ActivateNewQueue((int)priority));
        }

但是,收效甚微,我无法在有限的并行化环境中执行任务,从而导致大量构建,触发和完成100个API请求。 我可以使用Fiddler会话来告知这一点;

在此处输入图片说明

我已经阅读了一些有趣的文章和SO帖子( 在这里在这里这里 ),我认为这将详细说明如何执行此操作,但是到目前为止,我还无法弄清楚。 据我了解,lambda的async特性正在按设计的连续结构运行,该结构将生成的任务标记为已完成,基本上是“即时完成”。 这意味着,尽管队列工作正常,但在自定义调度程序上运行生成的Task<T>却成为问题。

这意味着,尽管队列工作正常,但事实证明这是在自定义调度程序上运行生成的任务的问题。

正确。 考虑它的一种方法[1]是将async方法拆分为多个任务-在每个await点将其分解。 然后,这些“子任务”中的每一个都在任务计划程序上运行。 因此, async方法完全在任务计划程序上运行(假设您不使用ConfigureAwait(false) ),但是在每次await ,它将离开任务计划程序,然后在await完成后重新输入该任务计划程序。

因此,如果您想在更高层次上协调异步工作,则需要采用其他方法。 可以为此编写代码,但可能会变得凌乱。 我建议您首先尝试TPL Dataflow库中的ActionBlock<T> ,将自定义任务计划程序传递给它的ExecutionDataflowBlockOptions

[1]这是一个简化。 状态机将避免创建实际的任务对象,除非有必要(在这种情况下,因为将它们安排在任务计划程序中,所以它们是必需的)。 同样,只有await未完成的 await点才真正导致“方法分裂”。

Stephen Cleary的答案很好地说明了为什么您不能为此使用TaskScheduler以及如何使用ActionBlock限制并行度。 但是,如果您想为此添加优先级,我认为您必须手动执行此操作。 您使用队列Dictionary的方法是合理的,简单的实现(不支持取消或完成)可能看起来像这样:

class Scheduler
{
    private static readonly Priority[] Priorities =
        (Priority[])Enum.GetValues(typeof(Priority));

    private readonly IReadOnlyDictionary<Priority, ConcurrentQueue<Func<Task>>> queues;
    private readonly ActionBlock<Func<Task>> executor;
    private readonly SemaphoreSlim semaphore;

    public Scheduler(int degreeOfParallelism)
    {
        queues = Priorities.ToDictionary(
            priority => priority, _ => new ConcurrentQueue<Func<Task>>());

        executor = new ActionBlock<Func<Task>>(
            invocation => invocation(),
            new ExecutionDataflowBlockOptions
            {
                MaxDegreeOfParallelism = degreeOfParallelism,
                BoundedCapacity = degreeOfParallelism
            });

        semaphore = new SemaphoreSlim(0);

        Task.Run(Watch);
    }

    private async Task Watch()
    {
        while (true)
        {
            await semaphore.WaitAsync();

            // find item with highest priority and send it for execution
            foreach (var priority in Priorities.Reverse())
            {
                Func<Task> invocation;
                if (queues[priority].TryDequeue(out invocation))
                {
                    await executor.SendAsync(invocation);
                }
            }
        }
    }

    public void Invoke(Func<Task> invocation, Priority priority)
    {
        queues[priority].Enqueue(invocation);
        semaphore.Release(1);
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM