关于等待几个任务的设计模式

时间:2013-01-11 09:22:17

标签: c# asynchronous async-await

这是我的方法。我有几个IEventProviders

interface IEventProvider
{
    Task<Event> GetEvent();
}

然后我有一个容器类来包装它们,并继续调用并等待GetEvent()等待下一个事件,例如套接字异步接收,定时器滴答等

class EventProviderContainer : IEventProvider
{
    private IEventProvider[] _providers;
    private Task<Event>[] _tasks;

    public EventProviderContainer(params IEventProvider[] providers)
    {
        _providers = providers;
    }

    public async Task<Event> GetEvent()
    {
        // Fill the _tasks first time we call the method.
        if (_tasks == null)
            _tasks = (from p in _providers select p.GetEvent()).ToArray();

        Task<Event> task = await Task<Event>.WhenAny(_tasks);

        // Get the provider index whose previous task is done.
        int index = Array.IndexOf(_tasks, task);
        // put next event of the provider into array.
        _tasks[index] = _providers[index].GetEvent();

        return await task;
    }
}

我认为这有点难看。这是一个更好的方法吗?

3 个答案:

答案 0 :(得分:2)

对于一项实际上并不那么简单的任务,您的代码非常简短且易于理解,而且我个人认为它并不丑陋。

除非您想要更改整个界面,否则我认为您无法找到更好的编写此代码的方法。我唯一要改变的是将_tasks初始化为构造函数(但也许你有理由)。

但我同意斯蒂芬的评论,对于事件,使用“推”语义通常比“拉”更合适。为此,Rx(IObservable<Event>)或TPL数据流(ISourceBlock<Event>)将非常有用。在这两种情况下,编写EventProviderContainer都会相对简单。两者中哪一个是更好的选择取决于你将如何处理结果。

答案 1 :(得分:1)

如果您希望每次为每个提供商提供一个活动,那么我建议您查看包含Interleaved方法的Processing tasks as they complete MSDN文章。此方法接受一组任务并返回一个新的任务数组,这些任务将按顺序完成。

另一方面,如果您希望在每个提供商到达时不断接收事件,那么我建议您查看Microsoft的Reactive Extensions (Rx)项目。

使用Rx,您的事件提供程序界面将变为:

public interface IEventProvider
{
    IObservable<Event> OnEvent();
}

然后,您的容器提供程序将使用Observable.Merge扩展方法来组合每个子提供程序的事件。

return _providers.Select(provider => provider.OnEvent()).Merge();

要实际接收事件,您可以通过附加每次新事件可用时执行的回调委托来订阅observable。

var provider = new EventProviderContainer(
    new TestEventProvider("a", 1000),
    new TestEventProvider("b", 1300),
    new TestEventProvider("c", 1600));
provider.OnEvent().Subscribe(Console.WriteLine);
Console.ReadLine();

以上示例使用测试事件提供程序,该提供程序使用Observable.Timer扩展方法以给定的时间段(以毫秒为单位)返回连续的事件流。

return Observable.Timer(TimeSpan.Zero, TimeSpan.FromMilliseconds(_period))
                 .Select(i => new TestEvent(_name, i));

答案 2 :(得分:0)

我认为您要实现的目标的正确代码是:

    public async Task<Event> GetEvent()
    {
        // Fill the _tasks first time we call the method.
        if (_tasks == null)
            _tasks = (from p in _providers select p.GetEvent()).ToArray();


        return await await Task<Event>.WhenAny(_tasks);
    }

await await似乎有点奇怪,但由于WhenAny()返回Task<Task<T>>,它必须是正确的。