假设:
IObservable<T> src
async Task F(T){...}
。F
只能按顺序调用。所以await F(x);await F(y);
很好,但Task.Factory.ContinueWhenAll(new[]{F(x),F(y)}, _ => {...});
是错误的,因为F(x)
和F(y)
不能同时运行。我很清楚await src.Do(F)
是错误的,因为它会同时运行F
。
我的问题是如何正确地做到这一点?
答案 0 :(得分:6)
Observable.Do
仅用于副作用,不用于顺序组合。 SelectMany
可能是你想要的。从Rx 2.0开始,SelectMany
存在重载,这使得使用Task<T>
组合可观察对象变得非常容易。 (请注意这些以及类似的Task / Observable coop运算符可能引入的additional concurrency。)
var q = from value in src
from _ in F(value.X).AsVoidAsync() // See helper definition below
from __ in F(value.Y).AsVoidAsync()
select value;
但是,根据您明确询问Do
运算符的事实,我怀疑 src 可能包含多个值,并且您不希望重叠调用每个值都 F 。在这种情况下,请考虑SelectMany
实际上就像Select->Merge
;因此,你可能想要的是Select->Concat
。
// using System.Reactive.Threading.Tasks
var q = src.Select(x => Observable.Defer(() => F(x).ToObservable())).Concat();
请勿忘记使用Defer
,因为F(x)
热。
AsVoidAsync Extension :
IObservable<T>
需要 T ,而Task
代表 void ,因此Rx的转化运算符要求我们获得{{来自Task<T>
的1}}。我倾向于使用Rx的Task
结构T
:
public static class TaskExtensions
{
public static Task<Unit> AsVoidAsync(this Task task)
{
return task.ContinueWith(t =>
{
var tcs = new TaskCompletionSource<Unit>();
if (t.IsCanceled)
{
tcs.SetCanceled();
}
else if (t.IsFaulted)
{
tcs.SetException(t.Exception);
}
else
{
tcs.SetResult(Unit.Default);
}
return tcs.Task;
},
TaskContinuationOptions.ExecuteSynchronously)
.Unwrap();
}
}
或者,您可以随时调用专门的System.Reactive.Unit方法。
答案 1 :(得分:1)
最简单的方法是使用TPL Dataflow,默认情况下会限制为单个并发调用,即使对于异步方法也是如此:
var block = new ActionBlock<T>(F);
src.Subscribe(block.AsObserver());
await block.Completion;
或者,您可以订阅一个自行节制的异步方法:
var semaphore = new SemaphoreSlim(1);
src.Do(async x =>
{
await semaphore.WaitAsync();
try
{
await F(x);
}
finally
{
semaphore.Release();
}
});