LoginSignup
9
7

More than 5 years have passed since last update.

複数値のI/Oを非同期処理して待機する

Posted at

ネイティブ非同期APIが用意されていないI/O処理を非同期処理して待機したい。
やり方、これで合ってるのかなぁ……?

単一値の場合は Task で包んで「はいどーぞ」なのだけど、複数値となるとこれは IObservable<T> の出番?
提供されているのは IEnumerable<T> を返すメソッドとする。

同期バージョン
public void DoSomething()
{
    // Workspace.GetPendingChangesEnumerable は各要素を取得するのに時間がかかるため同期処理すると画面がフリーズする
    IEnumerable<PendingChange> pendingChanges = workspace.GetPendingChangesEnumerable();

    foreach (var pendingChange in pendingChanges)
    {
        _files.Add(pendingChanges);
    }

    // なんか取得後に処理したいことがあるとする(ここではフィールドの書き換え)
    _completed = true;
}

これを処理するために ToObservable で非同期シーケンスにして、await で待機する。

拡張メソッドを用意する
public static class WorkspaceExtensions
{
    public static IObservable<PendingChange> GetPendingChangesObservable(this Workspace workspace)
    {
        return workspace.GetPendingChangesEnumerable().ToObservable(Scheduler.Default);
    }
}

非同期バージョン
public async Task DoSomething()
{
    // IObservable<T> にすれば各要素を非同期に取得できる
    // ※ Task<IEnumerable<T>> とは根本的に違う点に注意
    IObservable<PendingChange> pendingChanges = workspace.GetPendingChangesObservable();

    // IObservable<T> は awaitable (ToTask して await するのと同じ。Last が待機される)
    // await によって購読が始まるため Do メソッドでよい
    await pendingChanges.Do(pendingChange =>
    {
        _files.Add(pendingChanges);

    // 空の場合を考慮して DefaultIfEmpty しないと例外になる(await は Last を取るため)
    }).DefaultIfEmpty();

    // 全要素の処理が完了後にここが呼ばれる
    _completed = true;
}

呼び出し元を考えなければ SubscribeonCompleted で処理すればいいんだけど、呼び出し元には Task で返したかったのでこうなりました。

うーん、不安……

9
7
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
7