为异步执行排队操作/代理

时间:2014-02-07 13:01:04

标签: c# asynchronous .net-4.0 delegates

框架中是否有某些内容允许我异步执行委托队列?

我的意思是,我希望代表按照排队顺序一次执行一个,但我希望整个过程以异步方式运行。队列也没有固定,会定期添加其他委托,一旦到达队列顶部就应该处理。

我不需要特别使用Queue,而是我将如何描述所需的行为。

我可以自己写一些东西去做,但如果有内置的东西,我可以使用,而不是更好。

我简要地查看了ThreadPool.QueueUserWorkItem,因为它允许按顺序执行,但是可以找到一种令人满意的方法来防止一次执行多个。

2 个答案:

答案 0 :(得分:4)

  

框架中是否有允许我这样做的东西   异步执行委托队列?

我将其实现为自定义任务调度程序。然后,您可以将您的委托作为任务排队并运行,这将为您提供异常处理,取消和async/await的所有好处。

使用BlockingCollection实现一个以序列顺序执行代理的任务调度程序非常简单。以下SerialTaskSchedulerStephen Toub's StaTaskScheduler的简化版:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Console_21628490
{
    // Test
    class Program
    {
        static async Task DoWorkAsync()
        {
            using (var scheduler = new SerialTaskScheduler())
            {
                var tasks = Enumerable.Range(1, 10).Select(i =>
                    scheduler.Run(() =>
                    {
                        var sleep = 1000 / i;
                        Thread.Sleep(sleep);
                        Console.WriteLine("Task #" + i + ", sleep: " + sleep);
                    }, CancellationToken.None));

                await Task.WhenAll(tasks);
            }
        }

        static void Main(string[] args)
        {
            DoWorkAsync().Wait();
            Console.ReadLine();
        }
    }

    // SerialTaskScheduler
    public sealed class SerialTaskScheduler : TaskScheduler, IDisposable
    {
        Task _schedulerTask;
        BlockingCollection<Task> _tasks;
        Thread _schedulerThread;

        public SerialTaskScheduler()
        {
            _tasks = new BlockingCollection<Task>();

            _schedulerTask = Task.Run(() =>
            {
                _schedulerThread = Thread.CurrentThread;

                foreach (var task in _tasks.GetConsumingEnumerable())
                    TryExecuteTask(task);
            });
        }

        protected override void QueueTask(Task task)
        {
            _tasks.Add(task);
        }

        protected override IEnumerable<Task> GetScheduledTasks()
        {
            return _tasks.ToArray();
        }

        protected override bool TryExecuteTaskInline(
            Task task, bool taskWasPreviouslyQueued)
        {
            return _schedulerThread == Thread.CurrentThread &&
                TryExecuteTask(task);
        }

        public override int MaximumConcurrencyLevel
        {
            get { return 1; }
        }

        public void Dispose()
        {
            if (_schedulerTask != null)
            {
                _tasks.CompleteAdding();
                _schedulerTask.Wait();
                _tasks.Dispose();
                _tasks = null;
                _schedulerTask = null;
            }
        }

        public Task Run(Action action, CancellationToken token)
        {
            return Task.Factory.StartNew(action, token, TaskCreationOptions.None, this);
        }

        public Task Run(Func<Task> action, CancellationToken token)
        {
            return Task.Factory.StartNew(action, token, TaskCreationOptions.None, this).Unwrap();
        }

        public Task<T> Run<T>(Func<Task<T>> action, CancellationToken token)
        {
            return Task.Factory.StartNew(action, token, TaskCreationOptions.None, this).Unwrap();
        }
    }
}

输出:

Task #1, sleep: 1000
Task #2, sleep: 500
Task #3, sleep: 333
Task #4, sleep: 250
Task #5, sleep: 200
Task #6, sleep: 166
Task #7, sleep: 142
Task #8, sleep: 125
Task #9, sleep: 111
Task #10, sleep: 100

答案 1 :(得分:2)

您可以使用TPL dataflowActionBlock,只需排列一个包含Delegate的类和一系列参数。 ActionBlock将一次只执行一个代理。

var block = new ActionBlock<Item>(_ => _.Action.DynamicInvoke(_.Paramters));

class Item
{
    public Delegate Action { get; private set; }
    public object[] Parameters { get; private set; }

    public Item(Delegate action, object[] parameters)
    {
        Action = action;
        Parameters = parameters;
    }
}

更简单的选择是使用ActionBlock Action,但这会强制您捕获参数:

var block = new ActionBlock<Action>(action => action());

block.Post(() => Console.WriteLine(message));