いろきゅう.jp 〜Programmable maiden〜 Tech side このページをアンテナに追加 RSSフィード

2018-06-10

Task『一体いつから… IsCompleted == true でタスクが完了したと錯覚していた?』 わい「なん…だと……」で死亡

| 06:09

結論:単に自分のバグだっただけなんですけども(ってか Task をニワカで使ってるのが悪い)




以下のようなコードで以下のような出力が得られました:

// C#
static void Main(string[] args)
{
    Task task = Task.Run(() => daruiTask());
    Task.Delay(5).Wait();
    Console.WriteLine($"{nameof(task.IsCompleted)} ; {task.IsCompleted}");
    task.Wait();
    Console.WriteLine($"{nameof(task.IsCompleted)} ; {task.IsCompleted}");

    Console.WriteLine($"かえる");
    Task.Delay(1000).Wait();
}

static async void daruiTask()
{
    await Task.Delay(100);
    Console.WriteLine($"おしごと完了ヾ( ゚∀゚)ノ゙");
}

/**
stdout
======
IsCompleted ; True
IsCompleted ; True
かえる
おしごと完了ヾ( ゚∀゚)ノ゙
**/

「かえる」の後に「おしごと完了ヾ( ゚∀゚)ノ゙」しやがったー!!(ガビーン …ってか、IsCompleted が 常に true とは一体ウゴゴゴゴ…

って感じなんですが、結論としては daruiTask() の戻り値async void なのが悪いです。はい。 散々使うなよ!って言われ続けてるもの使っちゃったのが悪いです。はい。 async Task なら問題なく待ってくれます。はい。 本番コードで実装している際「戻り値不要だわ。void と…」って関数作った後に「async にしたほうがよくね!?」とか夜なべ中に軽くパニクりながら書いてたのが原因でした。はい。 すいませんでしたァ!!


↓問題ないコード

// C#
static void Main(string[] args)
{
    Task task = Task.Run(() => daruiTask());
    Task.Delay(5).Wait();
    Console.WriteLine($"{nameof(task.IsCompleted)} ; {task.IsCompleted}");
    task.Wait();
    Console.WriteLine($"{nameof(task.IsCompleted)} ; {task.IsCompleted}");

    Console.WriteLine($"かえる");
    Task.Delay(1000).Wait();
}

static async Task daruiTask()
{
    await Task.Delay(100);
    Console.WriteLine($"おしごと完了ヾ( ゚∀゚)ノ゙");
}

/**
stdout
======
IsCompleted ; False
おしごと完了ヾ( ゚∀゚)ノ゙
IsCompleted ; True
かえる
**/


async void を食わせると、無条件で IsCompleted が true 返すんですねぇ… そりゃそうか



Task でコールバックする関数を async にする意味とは?

実のところ、これがあんまり見えてないです。(上記の話題と関係あるようであんまり関係ない話なんですけど)

GUIスレッドから async な関数呼ぶのは理解できるんですよ。 「他のスレッドに処理を委譲して、俺は一旦帰ってメッセージポンプ回してくるわぁー。 よろしくなー!」って感じで。

それじゃぁ、Task でコールバックして既に他のスレッドに委譲している時に、非同期関数で更に別のスレッドに委譲しても良い状況にするとはなんぞやと。 別に GUI が固まるわけでも無いしなぁ…と。

個人的には、Taskがコールバックしたasync関数の中で await した → なんか重たい処理が走る → Taskの呼び出し元のタスクスケジューラーさんに一旦戻って、良い感じにスレッド/タスクマネジメントしてもらってね! …ってのはアリなような気はしていて、そんな意識の元 async な関数を Callback するのもアリなんじゃないかと勝手には思っています……が、.NET Framework 側でホントにそんなステキな事やってくれるのか全くの未確認(ぉ

# 非同期関数投げるんだったら、上記コードでも Task.Run(async () => await daruiTask()) にしないとダメとか、そんなら lambda 噛まさず直接関数投げろよとか色々ツッコミ所はあるんですがまぁ…まぁ……


じゃぁ、Task で Callback する関数内は同期関数だけに絞る? …まぁ普通にアリなのではと思います。 Task は「呼び出し元が継続するのは重たすぎてブロッキングしちゃうから処理を委譲する」わけで、ブロッキングする重たい処理を好きにすればいいじゃん。みたいなー。ていうかー

ただ、微妙に気になるのは CancellationToken で要キャンセルな環境になった時ですが…

Task cookTask = Task.Run(() => CookFile(srcFile));

public void CookFile(FileInfo srcFile)
{
    byte[] dekaiBuff = Cook(srcFile);
    using(FileStream fs = new FileStream(@"\\hayai-svr\c\spool", FileMode.Create, FileAccess.Write))
    {
        fs.Write(dekaiBuff, 0, dekaiBuff.Length);
        // ↑"速い"と思ったら、LAN自体は 10Base-5 で死亡
        // 1GB を転送しようとして 2.5時間ぐらい帰ってこない!
        // ネットワーク管理者出てこいよ!!!!
        // # 一気に 1GB 書くなよって話は勘弁してください(ぉ
    }
}

↓こんな感じにすりゃ、なんか良い感じに kill ってくれる…?(書いてて無理・危険な気がしてきた…

Task cookTask = Task.Run(() => CookFile(srcFile), cancelToken);

↓こっちの方がまだお行儀良さそう…?

Task cookTask = Task.Run(() => CookFile(srcFile, cancelToken)), cancelToken);

public void CookFile(FileInfo srcFile, CancellationToken cancelToken)
{
    byte[] dekaiBuff = Cook(srcFile);
    using(FileStream fs = new FileStream(@"\\hayai-svr\c\spool", FileMode.Create, FileAccess.Write))
    {
        fs.WriteAsync(dekaiBuff, 0, dekaiBuff.Length).Wait(cancelToken);
        // ↑同期関数内だけど、非同期関数を cancelToken 付きでブロッキング
    }
}

Task わかんねーわ

トラックバック - http://d.hatena.ne.jp/ir9Ex/20180610

2017-12-28

トラックバック - http://d.hatena.ne.jp/ir9Ex/20171228

2017-11-19

コンテナから目的の要素を抽出できなくて死亡

| 12:56

struct Doll
{
  int        id;
  std::string suffix;
};

void rozen()
{
  std::vector<int>  favIdList = { 202, 707 };  // ソート済み
  std::vector<Doll> dolls     = { // id でソート済み
    { 101, "dawa-"    },
    { 202, "kashira"  },
    { 303, "desu-"    },
    { 404, "dayo"     },
    { 505, "nanodawa" },
    { 606, "nanoyo"   },
    { 707, "..."      },
  };

  // { 202, "kashira" } と { 707, "..." } のインスタンスを抽出したい!
  std::vector<Doll> favDolls;
}

「favIdList ∩ dolls」 的なことをしたいというか、favIdList を利用して dolls選択だか射影したいというか、こういう時どーするのがセオリーなんですかねぇ…ってのが未だに良くわからなかったりします。

set_intersection() なんていう積集合を取ってくれるマンマな関数はあるんですけど、favIdList と dolls は型が違うので利用できないという話。仕方ないので idList を for で回し id を一つずつ抽出して equal_range() で dolls を lookup する?…といっても、やはり型が違うので無理ゲー

比較元IDだけを格納した Dollsインスタンス作るのもナシとします

// こんなの
const Dolls src = { id };

bool operator < (const Doll& lv, const Doll& rv)
{
  return lv.id < rv.id;
}

// エラーチェックしろよ!
const Dolls& fav = *std::equal_range(dolls.begin(), dolls.end(), src).first;


dolls を id をキーにした map 作るのもナシとします。ソート済みなのになんでソートするコンテナに入れなきゃならんのか!ダサい!

std::map<int, Doll> map;
std::transform(dolls.begin(), dolls.end(),
  std::inserter(map, map.begin()), [](const Doll& d){
    return std::move(std::make_pair(d.id, d));
});

// だからエラー(ry
const Doll& fav = *map.find(favId);

こうするのがシンプルですかねぇ…。線形感がモリモリ出てダサいけど…

auto itDolls = dolls.begin();
for(int id : favIdList)
{
  itDolls = std::find_if(itDolls, dolls.end(),
    [id](const Doll& d){ return id == d.id; }
  );
  favDolls.push_back(*itDolls++);
}

アルゴリズム関数やらなんやら組み合わせれば自前で創らず標準関数類だけで頑張れそうだけど、良くわからず。 umm......

kikairoyakikairoya 2017/11/19 19:38 https://wandbox.org/permlink/9A3qiaf5rszZM7oh

ir9ir9 2017/11/20 02:53 あーなるほど、ありがとうございます。
comp に lambda ぶち込む発想しか出てきてませんでしたわ… 頭硬い…

トラックバック - http://d.hatena.ne.jp/ir9Ex/20171119

2017-09-11

スクリーンセーバーをリモートデバッグしようとして死亡

| 02:33

スクリーンセーバー拡張子 exe を scr にしただけのものなので、exe をリモートデバッグするいつもの要領で環境を構築すればOK

…なんですが、引数として "/s" が必要でした。 ただ src ファイル名を指定しただけだと -1 (0xffffffff) が返されて起動しませんでしたうそーん!

Explorerからダブルクリックすりゃ起動するので "/s" とかのオプション必要ないのかと思ってたのにィ!

トラックバック - http://d.hatena.ne.jp/ir9Ex/20170911