BackacheEngineerの技術的な備忘録

技術系でいろいろ書けたらなーと

C# データの収集結果を非同期に UI へ追加する

何かしらの収集結果をUIに対して追加する際、収集結果をすべて用意してからUIに追加してるとユーザが操作できない時間ができてしまう。C# 8.0 か9.0 以降ならたぶん yield がいろいろ追加されてサクッと書ける。(そっちは勉強してない)

今回は .NET Framework 4.8 ということで既定バージョンの C# 7.3 を対象に、ユーザが常に操作できる状態でUIに値を追加していく。

await Task.Yield を使う

見出しのとおり、結論から言うと Task.Yield メソッドを呼び出すとうまいこと制御を返してくれる。注意するのは、そのまんま単純に書くと想定した動きにならないこと。Delay をかけてやらないとダメ。なんでダメなのかを勉強しなきゃいけない。表示のために間がないとダメってのは感覚でわかるが、ちゃんと理論的に知っておかないと痛い目を見そう・・・。

サンプル下記のとおり(省略しすぎてちょっとひどいサンプルだ・・・)。 やってるのはStackPanelにTextBlockを追加していくだけ。 別にTextBlockじゃなくていいし、なんならStackPanelじゃなくていいんだけど、そこは今回の勉強の目的のためってことで。 下記をClickイベントとかに書いてやればいい。await 使うので Click イベントメソッドには async 必須。

var cts = new CancellationTokenSource();

foreach (var test in list) // list は 10000 個の文字列
{
    var txtblk = new TextBlock
    {
        FontSize = 12,
        Text = test
    };
    stp.Children.Add(txtblk); // stp は xaml とかのデザイン側にあるstack panel
    try
    {
        // Delay を 0.1 秒かける。これで 0.1 秒間待って、そのまま Task.Yield() により一旦制御が戻る
        // その後、そのままループが回る
        await Task.Delay(100).ContinueWith(_ =>
        {
            cts.Token.ThrowIfCancellationRequested();
            Task.Yield();
        }, cts.Token);
    }
    catch (OperationCanceledException)
    {
        // なんかキャンセル処理
        return;
    }
    catch (Exception)
    {
        // 例外処理
        return;
    }
}

ちょっとしたツールだとこんな感じで動いてくれる。

f:id:BackacheEngineer:20210327173819g:plain
TextBlockが次々と出力される

制御が返ってきているのでちゃんとスクロールバーを動かせるようになってる。 データ作って表示が完全に完了するまで待つのもいいけど、待つ時間によってはイライラするので制御戻したいよねって話でした。