同时等待多个任务的设计模式
本文关键字:任务 设计模式 等待 | 更新日期: 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>>
,它必须是正确的。