同时等待多个任务的设计模式

本文关键字:任务 设计模式 等待 | 更新日期: 2023-09-27 17:52:40

这是我的方法。我有几个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;
    }
}

我觉得它有点丑。这是更好的方法吗?

同时等待多个任务的设计模式

对于一个实际上不是那么简单的任务,你的代码是相当短的,可以理解的,我个人不认为它是丑陋的。

我不认为你会找到一个明显更好的方法来写这段代码,除非你想改变你的整个接口。我唯一要改变的是将初始化_tasks移到构造函数中(但也许你有这样做的理由)。

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

如果您希望一次为每个提供者提供一个事件,那么我建议您查看处理任务,因为它们完成了MSDN文章,其中包含一个Interleaved方法。此方法接受一个任务集合,并返回一个新的任务数组,该数组将按完成顺序产生结果。

另一方面,如果你想在每个提供者到达时持续接收事件,那么我建议你看看微软的响应式扩展(Reactive Extensions, Rx)项目。

使用Rx,您的事件提供程序接口将变成如下内容:

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

你的容器提供者就会使用Observable。合并扩展方法,以组合每个子提供程序的事件。

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

要实际接收事件,你需要通过附加一个回调委托来订阅可观察对象,该委托在每次有新事件可用时执行。

var provider = new EventProviderContainer(
    new TestEventProvider("a", 1000),
    new TestEventProvider("b", 1300),
    new TestEventProvider("c", 1600));
provider.OnEvent().Subscribe(Console.WriteLine);
Console.ReadLine();
上面的例子使用了一个测试事件提供程序,它使用Observable在给定的时间内以毫秒为单位返回连续的事件流。定时器扩展方法。
return Observable.Timer(TimeSpan.Zero, TimeSpan.FromMilliseconds(_period))
                 .Select(i => new TestEvent(_name, i));

我认为你想要实现的正确代码是:

    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>>,它必须是正确的。