我的解决方案中有两个项目:WPF项目和类库。
在我的课程库中:
我有一个符号列表:
class Symbol
{
Identifier Identifier {get;set;}
List<Quote> HistoricalQuotes {get;set;}
List<Financial> HistoricalFinancials {get;set;}
}
对于每个符号,我查询金融服务,以使用webrequest检索每个符号的历史财务数据。 (webClient.DownloadStringTaskAsync(URI))
所以这是我的方法:
public async Task<IEnumerable<Symbol>> GetSymbolsAsync()
{
var historicalFinancialTask = new List<Task<HistoricalFinancialResult>>();
foreach (var symbol in await _listSymbols)
{
historicalFinancialTask.Add(GetFinancialsQueryAsync(symbol));
}
while (historicalFinancialTask.Count > 0)
{
var historicalFinancial = await Task.WhenAny(historicalFinancialTask);
historicalFinancialTask.Remove(historicalFinancial);
// the line below doesn't compile, which is understandable because method's return type is a Task of something
yield return new Symbol(historicalFinancial.Result.Symbol.Identifier, historicalFinancial.Result.Symbol.HistoricalQuotes, historicalFinancial.Result.Data);
}
}
private async Task<HistoricalFinancialResult> GetFinancialsQueryAsync(Symbol symbol)
{
var result = new HistoricalFinancialResult();
result.Symbol = symbol;
result.Data = await _financialsQuery.GetFinancialsQuery(symbol.Identifier); // contains some logic like parsing and use WebClient to query asynchronously
return result;
}
private class HistoricalFinancialResult
{
public Symbol Symbol { get; set; }
public IEnumerable<Financial> Data { get; set; }
// equality members
}
正如您所看到的,我希望每次下载每个符号的财务历史数据时,都会产生结果,而不是等待我对金融服务的所有调用完成。
在我的WPF中,这就是我想要做的事情:
foreach(var symbol in await _service.GetSymbolsAsync())
{
SymbolsObservableCollection.Add(symbol);
}
似乎我们不能在异步方法中产生回报,那么我可以使用什么解决方案?除了将我的GetSymbols方法移动到我的WPF项目中。
答案 0 :(得分:41)
虽然我喜欢TPL Dataflow组件(svick建议您使用),但转移到该系统确实需要大量的承诺 - 它不是您可以添加到现有设计中的东西。如果您正在执行大量CPU密集型数据处理并希望利用许多CPU内核,它可以提供相当大的好处。但要充分利用它是非常重要的。
他的其他建议,使用Rx,可能更容易与现有解决方案集成。 (请参阅original documentation,但有关最新代码,请使用Rx-Main nuget包。或者,如果您想查看来源,请参阅the Rx CodePlex site)它甚至会如果你愿意,调用代码可以继续使用IEnumerable<Symbol>
- 您可以将Rx纯粹用作实现细节,[编辑2013/11/09添加:]尽管作为svick已经指出,鉴于你的最终目标,这可能不是一个好主意。
在我向您展示一个例子之前,我想清楚一下我们究竟在做什么。您的示例有一个带有此签名的方法:
public async Task<IEnumerable<Symbol>> GetSymbolsAsync()
返回类型Task<IEnumerable<Symbol>>
,基本上是&#34;这是一种产生类型IEnumerable<Symbol>
的单个结果的方法,它可能不会立即产生该结果。&#34; < / p>
单一结果位,我认为这会让你感到悲伤,因为那并不是你想要的。 Task<T>
(无论T
可能是什么)表示单个异步操作。它可能有许多步骤(await
的许多用法,如果你将它作为C#async
方法实现),但最终会产生一件事。您希望在不同的时间生成多个内容,因此Task<T>
不适合。
如果你真的想做你的方法签名所承诺的 - 最终产生一个结果 - 你可以做的一种方法是让你的异步方法构建一个列表,然后在它好的时候产生它准备好了:
// Note: this first example is *not* what you want.
// However, it is what your method's signature promises to do.
public async Task<IEnumerable<Symbol>> GetSymbolsAsync()
{
var historicalFinancialTask = new List<Task<HistoricalFinancialResult>>();
foreach (var symbol in await _listSymbols)
{
historicalFinancialTask.Add(GetFinancialsQueryAsync(symbol));
}
var results = new List<Symbol>();
while (historicalFinancialTask.Count > 0)
{
var historicalFinancial = await Task.WhenAny(historicalFinancialTask);
historicalFinancialTask.Remove(historicalFinancial);
results.Add(new Symbol(historicalFinancial.Result.Symbol.Identifier, historicalFinancial.Result.Symbol.HistoricalQuotes, historicalFinancial.Result.Data));
}
return results;
}
此方法执行其签名所示:它异步生成一系列符号。
但是大概你想创建一个IEnumerable<Symbol>
来生成可用的项目,而不是等到它们全部可用。 (否则,您也可以使用WhenAll
。)您可以这样做,但yield return
不是这样。
简而言之,我认为你想要做的是产生一个异步列表。其中有一种类型:IObservable<T>
表达了我认为您希望用Task<IEnumerable<Symbol>>
表达的内容:它是一系列项目(就像IEnumerable<T>
一样)但是异步的。
通过类比来理解它可能有所帮助:
public Symbol GetSymbol() ...
是
public Task<Symbol> GetSymbolAsync() ...
作为
public IEnumerable<Symbol> GetSymbols() ...
是:
public IObservable<Symbol> GetSymbolsObservable() ...
(不幸的是,与Task<T>
不同,对于所谓的异步序列导向方法,我们没有通用的命名约定。我最后添加了“Observable”###########################################################在这里,但这不是普遍的做法。我当然不会称之为GetSymbolsAsync
因为人们希望返回Task
。)
换句话说,Task<IEnumerable<T>>
说&#34;当我做好并准备好时,我会制作这个系列&#34;而IObservable<T>
说:&#34;这是一个集合。当我做好并做好准备时,我会生产每件商品。&#34;
因此,您需要一个返回Symbol
个对象序列的方法,其中这些对象是异步生成的。这告诉我们你应该真的回归IObservable<Symbol>
。这是一个实现:
// Unlike this first example, this *is* what you want.
public IObservable<Symbol> GetSymbolsRx()
{
return Observable.Create<Symbol>(async obs =>
{
var historicalFinancialTask = new List<Task<HistoricalFinancialResult>>();
foreach (var symbol in await _listSymbols)
{
historicalFinancialTask.Add(GetFinancialsQueryAsync(symbol));
}
while (historicalFinancialTask.Count > 0)
{
var historicalFinancial = await Task.WhenAny(historicalFinancialTask);
historicalFinancialTask.Remove(historicalFinancial);
obs.OnNext(new Symbol(historicalFinancial.Result.Symbol.Identifier, historicalFinancial.Result.Symbol.HistoricalQuotes, historicalFinancial.Result.Data));
}
});
}
正如您所看到的,这可以让您写出您希望编写的内容 - 此代码的主体几乎与您的相同。唯一的区别是你使用yield return
(没有编译)的地方,这会调用Rx提供的对象上的OnNext
方法。
写完之后,您可以轻松地将其打包成IEnumerable<Symbol>
([编辑2013/11/29添加:],尽管您可能实际上并不想做这个 - 在答案结尾处看到补充):
public IEnumerable<Symbol> GetSymbols()
{
return GetSymbolsRx().ToEnumerable();
}
这可能看起来不是异步的,但事实上它确实允许底层代码异步操作。当您调用此方法时,它不会阻止 - 即使执行获取财务信息的基础代码无法立即生成结果,此方法仍将立即返回IEnumerable<Symbol>
。当然,如果数据尚不可用,任何试图遍历该集合的代码都将最终阻塞。但至关重要的是,我认为你最初想要实现的目标是什么:
async
方法来完成工作(我的示例中的委托,作为参数传递给Observable.Create<T>
,但如果您愿意,可以编写独立的async
方法)IEnumerable<Symbol>
会在每个项目可用时立即生成这是有效的,因为Rx的ToEnumerable
方法中有一些聪明的代码,它弥合了IEnumerable<T>
的同步世界视图与异步生成结果之间的差距。 (换句话说,这确实让你感到失望的是发现C#无法为你做的。)
如果您有点好奇,可以查看来源。可以在https://rx.codeplex.com/SourceControl/latest#Rx.NET/Source/System.Reactive.Linq/Reactive/Linq/Observable/GetEnumerator.cs
找到代码ToEnumerable
所依据的代码
[编辑2013/11/29添加:]
ObservableCollection<Symbol>
。不知怎的,我没有看到那一点。这意味着IEnumerable<T>
是错误的方式 - 您希望在项目可用时填充集合,而不是使用foreach
循环。所以你只需这样做:
GetSymbolsRx().Subscribe(symbol => SymbolsObservableCollection.Add(symbol));
或类似的东西。这将在集合可用时将项目添加到集合中。
这取决于顺便在UI线程上启动的整个事情。只要是这样,您的异步代码最终应该在UI线程上运行,这意味着当项目添加到集合时,也会在UI线程上发生。但是如果由于某种原因你最终从工作线程启动了东西(或者如果你在任何等待中使用ConfigureAwait
,从而打破了与UI线程的连接),你需要安排到处理右线程上Rx流中的项目:
GetSymbolsRx()
.ObserveOnDispatcher()
.Subscribe(symbol => SymbolsObservableCollection.Add(symbol));
如果您在执行此操作时处于UI线程中,它将选择当前的调度程序,并确保所有通知都通过它进行。如果您在订阅时已经使用了错误的线程,则可以使用带有调度程序的ObserveOn
重载。 (这些要求您引用System.Reactive.Windows.Threading
。这些是扩展方法,因此您需要using
来包含名称空间,也称为System.Reactive.Windows.Threading
)< / p>
答案 1 :(得分:6)
您要求的内容没有多大意义,因为IEnumerable<T>
是一个同步界面。换句话说,如果某个项尚不可用,MoveNext()
方法必须阻止,则别无选择。
您需要的是IEnumerable<T>
的某种异步版本。为此,您可以使用来自TPL dataflow的Rx或(我最喜欢的)块中的IObservable<T>
。有了它,你的代码可能看起来像这样(我还将一些变量更改为更好的名称):
public IReceivableSourceBlock<Symbol> GetSymbolsAsync()
{
var block = new BufferBlock<Symbol>();
GetSymbolsAsyncCore(block).ContinueWith(
task => ((IDataflowBlock)block).Fault(task.Exception),
TaskContinuationOptions.NotOnRanToCompletion);
return block;
}
private async Task GetSymbolsAsyncCore(ITargetBlock<Symbol> block)
{
// snip
while (historicalFinancialTasks.Count > 0)
{
var historicalFinancialTask =
await Task.WhenAny(historicalFinancialTasks);
historicalFinancialTasks.Remove(historicalFinancialTask);
var historicalFinancial = historicalFinancialTask.Result;
var symbol = new Symbol(
historicalFinancial.Symbol.Identifier,
historicalFinancial.Symbol.HistoricalQuotes,
historicalFinancial.Data);
await block.SendAsync(symbol);
}
}
用法可能是:
var symbols = _service.GetSymbolsAsync();
while (await symbols.OutputAvailableAsync())
{
Symbol symbol;
while (symbols.TryReceive(out symbol))
SymbolsObservableCollection.Add(symbol);
}
或者:
var symbols = _service.GetSymbolsAsync();
var addToCollectionBlock = new ActionBlock<Symbol>(
symbol => SymbolsObservableCollection.Add(symbol));
symbols.LinkTo(
addToCollectionBlock, new DataflowLinkOptions { PropagateCompletion = true });
await symbols.Completion;
答案 2 :(得分:0)
我相信你也无法将async
方法作为迭代器方法。这是.NET的限制。看一下使用Task Parallel Library Dataflow,它可用于处理数据,因为它可用。还有Reactive Extensions.
答案 3 :(得分:0)
为什么不这样做:
public async IEnumerable<Task<Symbol>> GetSymbolsAsync()
{
var historicalFinancialTask = new List<Task<HistoricalFinancialResult>>();
foreach (var symbol in await _listSymbols)
{
historicalFinancialTask.Add(GetFinancialsQueryAsync(symbol));
}
while (historicalFinancialTask.Count > 0)
{
var historicalFinancial = await Task.WhenAny(historicalFinancialTask);
historicalFinancialTask.Remove(historicalFinancial);
yield return new Symbol(historicalFinancial.Result.Symbol.Identifier, historicalFinancial.Result.Symbol.HistoricalQuotes, historicalFinancial.Result.Data);
}
}