[英]Observable.Using with async Task
我已經使用了Observable.Using和返回IDisposable的方法:
Observable.Using(() => new Stream(), s => DoSomething(s));
但是,當異步創建流時,我們將如何繼續? 像這樣:
Observable.Using(async () => await CreateStream(), s => DoSomething(s));
async Task<Stream> CreateStream()
{
...
}
DoSomething(Stream s)
{
...
}
這不會編譯,因為它表示s
是Task<Stream>
而不是Stream
。
這是怎么回事?
我們來看看Observable.Using
的異步重載的源代碼:
Observable.FromAsync(resourceFactoryAsync)
.SelectMany(resource => Observable.Using(() => resource, r =>
Observable.FromAsync(ct => observableFactoryAsync(r, ct)).Merge()));
知道它只是在引擎蓋下使用同步版本,你可以做這樣的事情來適應你的用法:
Observable.FromAsync(CreateStream)
.SelectMany(stream => Observable.Using(() => stream, DoSomething));
不幸的是,不包括過載,但您可以隨時創建自己的:
public static class ObservableEx
{
public static IObservable<TSource> Using<TSource, TResource>(
Func<Task<TResource>> resourceFactoryAsync,
Func<TResource, IObservable<TSource>> observableFactory)
where TResource : IDisposable =>
Observable.FromAsync(resourceFactoryAsync).SelectMany(
resource => Observable.Using(() => resource, observableFactory));
}
然后就像這樣簡單:
ObservableEx.Using(CreateStream, DoSomething);
所有這一切都假設DoSomething
返回一個observable,在你的問題中沒有提到,但是Observable.Using
的合同要求。
所以這里的問題是有兩個帶有非常笨拙的類型簽名的重載。
當你傳遞一個Task<TResource>
返回函數作為第一個參數時,它似乎推斷第二個參數,即“可觀察工廠”,也將返回一個Task
但是推理然后失敗,因為在你的例子中,沒有參數由任一函數聲明為取消令牌,並且都需要該參數。 推理以無數種方式失敗,而且令人困惑。
我將嘗試用一個現實的例子來看待它。
注意:編寫如下代碼可能是一個壞主意,但它至少是真實世界類型的用例。
var connectionString = @"Data Source=.\SQLEXPRESS;Integrated Security=SSPI;app=LINQPad";
Observable.Using(
resourceFactoryAsync: async ct => await ConnectAsync(connectionString, ct),
observableFactoryAsync: async (connection, ct) => await QueryAsync(connection, ct)
)
.Subscribe(
onNext: Console.WriteLiner,
onError: Console.Error.WriteLine
);
async Task<IObservable<string>> QueryAsync(SqlConnection c, CancellationToken ct = default)
{
var command = c.CreateCommand();
command.CommandText = "select * from Categories as c order by c.CategoryId";
var enumerableQuery =
from IDataRecord record in await command.ExecuteReaderAsync(ct)
select (string)record["CategoryName"];
return enumerableQuery.ToObservable();
}
async Task<SqlConnection> ConnectAsync(string cs, CancellationToken ct = default)
{
var connection = new SqlConnection(cs);
await connection.OpenAsync(ct);
return connection;
}
注意:使用命名參數只是為了記錄正在選擇哪個重載。
這是我能想到的最好的但是可以用更好的例子來編輯它。
有些值得注意的事情是
如果為resourceFactoryAsync
或observableFactoryAsync
提供了一個nullary函數,所有這一切都會崩潰,編譯失敗。 您必須將CancellationToken
聲明為資源工廠和可觀察工廠函數的形式參數。 為簡潔起見,它們都在上面的代碼中命名為ct
。 雖然我們只是傳遞它們,但重要的是它們被宣布 。
請注意, observableFactoryAsync
的connection
參數不是等待的,而是實際的資源。 您收到的錯誤提示您自然地假設應該等待資源,這是由於類型推斷選擇了同步過載,因此將TResource
作為Task<X>
轉發到下一個也被假定為同步的函數中。
這些抽象的構成方式感覺相當尷尬,至少對我而言。 我們正在混合多種異步和同步編程風格,我們將IDisposable
混合到其中,以便我們以異步方式顯式創建它們,但是誰知道它們將如何處理。
QueryAsync
函數實際上創建了一個輔助IDisposable
,一個SqlCommand
,我通常將其包裝在一個using
塊中,但不能,因為我需要對保存的查詢進行惰性求值,所以我需要在調用閉包時調用該命令。
可能有更好的方法來做到這一點,但我想知道它是否涉及調用Observable.Using
多次使用然后合並它們。 我現在基本上只是漫無目的地討論Observable monad可以感覺到的復雜程度(授予我新手Rx用戶)但是它真的感覺它想要將整個應用程序包含在其中。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.