簡體   English   中英

Observable.Using with async Task

[英]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)
{
    ...
}

這不會編譯,因為它表示sTask<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;
}

注意:使用命名參數只是為了記錄正在選擇哪個重載。

這是我能想到的最好的但是可以用更好的例子來編輯它。

有些值得注意的事情是

  • 如果為resourceFactoryAsyncobservableFactoryAsync提供了一個nullary函數,所有這一切都會崩潰,編譯失敗。 您必須將CancellationToken聲明為資源工廠和可觀察工廠函數的形式參數。 為簡潔起見,它們都在上面的代碼中命名為ct 雖然我們只是傳遞它們,但重要的是它們被宣布

  • 請注意, observableFactoryAsyncconnection參數不是等待的,而是實際的資源。 您收到的錯誤提示您自然地假設應該等待資源,這是由於類型推斷選擇了同步過載,因此將TResource作為Task<X>轉發到下一個也被假定為同步的函數中。

  • 這些抽象的構成方式感覺相當尷尬,至少對我而言。 我們正在混合多種異步和同步編程風格,我們將IDisposable混合到其中,以便我們以異步方式顯式創建它們,但是誰知道它們將如何處理。

  • QueryAsync函數實際上創建了一個輔助IDisposable ,一個SqlCommand ,我通常將其包裝在一個using塊中,但不能,因為我需要對保存的查詢進行惰性求值,所以我需要在調用閉包時調用該命令。

  • 可能有更好的方法來做到這一點,但我想知道它是否涉及調用Observable.Using多次使用然后合並它們。 我現在基本上只是漫無目的地討論Observable monad可以感覺到的復雜程度(授予我新手Rx用戶)但是它真的感覺它想要將整個應用程序包含在其中。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM