在我的 C# 应用程序中结合使用 Task 和 IObservable 是一种不好的做法吗?



我最近进入了Rx,我正在使用它来帮助我从数据挖掘应用程序中的多个API中提取数据。

我有一个为每个 API 实现的接口,它封装了对每个 API 的常见调用,例如

public interface IMyApi {
    IObservable<string> GetApiName(); //Cold feed for getting the API's name.
    IObservable<int> GetNumberFeed(); //Hot feed of numbers from the API
}

我的问题是关于冷IObservables与任务。在我看来,冷可观察基本上是一项任务,它们的运作方式大致相同。把一个任务"抽象"为一个冷冰冰的可观察对象,这让我感到很奇怪,而你可以争辩说一个任务就是你所需要的。此外,使用冷可观察量来包装任务会隐藏活动的性质,因为签名看起来与热可观察量相同。

我可以表示上述界面的另一种方法是:

public interface IMyApi {
    Task<string> GetApiNameAsync(); //Async method for getting the API's name.
    IObservable<int> GetNumberFeed(); //Hot feed of numbers from the API
}

关于为什么我不应该在任务和 IObservable 之间混合搭配,是否有一些传统智慧?

编辑:澄清 - 我已经阅读了发布的其他讨论并了解 Rx 和 TPL 之间的关系,但我担心的主要是在应用程序中将两者结合起来是否安全,以及它是否会导致不良实践或线程和调度陷阱?

我的问题是关于冷IObservables与任务。在我看来,冷可观察量基本上是一项任务,它们的运行方式大致相同。

重要的是要注意,事实并非如此,它们非常不同。这是核心区别:

// Nothing happens here at all! Just like calling Enumerable.Range(0, 100000000)
// doesn't actually create a huge array, until I use foreach.
var myColdObservable = MakeANetworkRequestObservable();
// Two network requests made!
myColdObservable.Subscribe(x => /*...*/);
myColdObservable.Subscribe(x => /*...*/);
// Only ***one*** network request made, subscribers share the
// result
var myTaskObservable = MakeATask().ToObservable();
myTaskObservable.Subscribe(x => /*...*/);
myTaskObservable.Subscribe(x => /*...*/);

为什么这很重要?Rx 中的几种方法(如 Retry)依赖于此行为:

// Retries three times, then gives up
myColdObservable.Retry(3).Subscribe(x => /*...*/);
// Actually *never* retries, and is effectively the same as if the
// Retry were never there, since all three tries will get the same
// result!
myTaskObservable.Retry(3).Subscribe(x => /*...*/);

所以一般来说,让你的可观察量变通常会让你的生活更轻松。

如何使任务变冷?

使用 Defer 运算符:

var obs = Observable.Defer(() => CreateATask().ToObservable());
// CreateATask called *twice* here
obs.Subscribe(/*...*/);
obs.Subscribe(/*...*/);

混合模型没有问题,事实上,即使是 Rx 团队也在 Rx 中包含了许多自适应运算符。例如,ToTaskToObservableSelectManyDeferAsyncStartAsyncToAsync等。您甚至可以在async方法中await IObservable<T>

应该影响决策的主要区别是基数:

IObservable<T>为 [0,∞]

Task<T>为 [0,1]

因此,如果您只需要表示单个返回值,则强烈建议考虑使用 Task<T>

TaskIObservable 之间的区别不是热与冷:Task -return 方法几乎可以是"冷"(每次调用都返回新Task)或"热"(始终返回相同的Task),就像 IObservable s 一样。

两者之间的区别在于,IObservable表示一系列结果,而Task表示单个结果

因此,如果您总是有一个结果(或错误),请使用 Task ,当您可以有任意数量的结果时,请使用 IObservable

相关内容

  • 没有找到相关文章

最新更新