質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

91.35%

  • C#

    4756questions

    C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

C#で2つのTaskを同時実行かつGUIとは別スレッドで実行したい

解決済

回答 2

投稿 2017/11/21 17:03

  • 評価
  • クリップ 1
  • VIEW 121

x25m

score 84

2つの株の板情報を取得するアプリケーションを作っています。
毎秒取得するので、2つのURLに同時アクセスして取得したいのですが、同時取得はできましたがGUIのフリーズが頻発してしまいます。

// 2つのタスクを作ってほぼ同時にアクセスします。
// GetPriceLevel1()とGetPriceLevel2()はURLにアクセスして板情報を取得する関数です。
Task<int> task1 = Task.Run(() => GetPriceLevel1());
Task<int> task2 = Task.Run(() => GetPriceLevel2());

// 2つの板情報が取得されるまで待ちます。
Task.WaitAll(task1, task2);

// 板情報が取得されたら出力します。
Console.WrightLine("板1:" + task1.Result.ToString());
Console.WrightLine("板2:" + task2.Result.ToString());

ここまではうまくいきました。ほぼ同時に2つの板にアクセスして情報を取得できています。

このコードを関数化し、GUIのFormにSystem.Threading.Timerなどを使って毎秒取得するとGUIがフリーズしてしまいます。
必ずフリーズするのではなく、頻発する感じです。

いろいろ調べていると

同期メソッドから非同期メソッドを呼び出すとアプリケーションがフリーズする - 非同期メソッド呼び出しによるデッドロック

という記事を見つけました。
Task.RunにConfigureAwait(false)メソッドを指定するとGUIスレッドとは別のスレッドで処理してくれるそうです。
しかし、私のコードでは2つのタスクがあるので、

int task1 = await Task.Run(() => GetPriceLevel1()).ConfigureAwait(false);
int task2 = await Task.Run(() => GetPriceLevel2()).ConfigureAwait(false);

// Task.WaitAll(task1, task2); ←上記で2awaitを指定しているのでこれは指定できない。

のようにしてしまうと同時にGetPriceLevel1()とGetPriceLevel2()を実行できません。

2つの関数を同時に実行でき、かつGUIのスレッドとは別のスレッドでTaskを実行するにはどのようにすればよいのでしょうか?
どうぞよろしくお願い致します。

環境:
Windows10 + Visual Studio 2015

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+1

Task.WaitAllなんて使ったらそりゃ止まるでしょ。Waitしているのだから。

このような形で、非同期で待たないとだめ。

await Task.WhenAll(task..)

やりたいことを美しく実現しているのが多分これ。
http://engineering.grani.jp/entry/2017/04/06/163904

投稿 2017/11/21 17:29

編集 2017/11/21 18:33

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/11/21 18:09

    ありがとうございます。
    ざっと読んでみたのですが、Taskが呼ばれた後にGUIスレッドに戻らない仕様なのでしょうか?
    ValueTupleを使うとスレッドプールを有効活用するという記述が他の検索をしても見つかりませんでした。
    並列実行はできているのですがGUIが固まってしまうのをなんとかしたい感じなのです。

    キャンセル

  • 2017/11/21 18:22

    そもそも的に、WaitAllを使っているのが悪の元凶という感じがします。初めに挙げたURLのように、WhenAllを使うようにすると期待どおりになるんじゃないかな。

    キャンセル

  • 2017/11/21 21:24

    あ、すみません・・・WhenAllと書いてあったのですね。WaitAllだと勘違いしていました。
    それで調べてみたら「TaskのWaitはデッドロックが起こるから使ったらいけない」と書いていますね。
    WhenAllに変えてみたらデッドロックが起こらなくなりました!

    キャンセル

+1

こんにちは。

このコードを関数化し

の内容が分かりませんが、async関数ではなく普通の関数にしたと仮定して回答します。

GetPriceLevel1()やGetPriceLevel2()の処理が次の1秒処理開始までに終了していないのではないでしょうか? その結果、同じクライアントからの同じ要求のシーケンスが複数入り混じって発生して要求と応答の対応が取れず、おかしくなっている可能性が疑わしく感じます。

前の処理が終わってから次の処理を開始するには、System.Windows.Forms.Timerを使うと簡単です。
こちらはメイン・スレッドで次処理を開始しますから、関数化した関数から抜けるまで次の処理は始まりません。

ちなみに、ConfigureAwait(false)は後続の処理をTask.Run()が起動したスレッドで行う指定だそうですからTask.Run()に指定した処理GetPriceLevel1()が終わってから次に進むことになります。


【蛇足ですが】
多くのサイトでConfigureAwait(false)を使うことが推奨されているようですが、メイン・スレッドと同期する機能がありがたいasync関数のその機能を完全にキャンセルするのなら、最初からasync関数にしない方が良いような気がします。単にTaskで十分です。そのほうが簡単ですし。

投稿 2017/11/21 18:04

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/11/21 19:56

    ご回答ありがとうございます。

    >GetPriceLevel1()やGetPriceLevel2()の処理が次の1秒処理開始までに終了していないのではないでしょうか?

    いえ、それらの関数が次の1秒開始までに終了していない場合は次の処理をスルーするようにしています。

    System.Windows.Forms.Timerですが、こちらは精度が悪いのと、タイマーを動かしているとGUIは固まってしまわないでしょうか?

    蛇足の件ですが、なかなか難しいところです。
    待ったほうがいい処理と、待たないでいい処理がごっちゃになっていて自分でもよくわからなくなっている状態です。

    現状で欲しい機能としては、「2つのURLに同時にアクセスしたい」という点です。
    質問の最初に示したコードでは、同時アクセスは実現できているのですが、GUIが数分固まってしまうことが多く、この点を解決したいのです。

    「GetPriceLevel1()とGetPriceLevel2()で板情報を同時に取得し、Formに板情報を表示し、ボタンを押すと注文を実行する」みたいなことを実現したいのですが、同時処理をするとなぜかGUIが固まってしまいます。
    順番にawaitでGetPriceLevel1()を実行、完了したらGetPriceLevel2()を実行とすればGUIが固まることはないのですが、これだと数ms単位で変化する板情報に時間差がでてしまって正確ではありません。

    どのようにすればこれが実現できるのでしょうか・・・。

    キャンセル

  • 2017/11/21 21:40

    > いえ、それらの関数が次の1秒開始までに終了していない場合は次の処理をスルーするようにしています。

    なるほど。その機能が適切に動作していたら問題ない筈ですね。

    > System.Windows.Forms.Timerですが、こちらは精度が悪いのと、タイマーを動かしているとGUIは固まってしまわないでしょうか?

    タイマー処理を実行中は当然GUIは動かないです。Wait()しているから結構つらいですね。考え不足でした。

    > 現状で欲しい機能としては、「2つのURLに同時にアクセスしたい」という点です。

    「ここまではうまくいきました。ほぼ同時に2つの板にアクセスして情報を取得できています。」と書かれていますので、「2つのURLに同時にアクセスしたい」はできていると認識しています。
    そして、1秒間隔でそれをやろうとするとGUIが固まる問題があると理解しています。ですので、疑うべきは「1秒間隔で実行している」手順です。それを提示されていないのでなんとも。

    キャンセル

  • 2017/11/23 16:22

    ありがとうございます。
    とりあえずkiichi54321さんの方法(WhenAll)を使う方法でデッドロックは回避できました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

ただいまの回答率

91.35%

関連した質問

同じタグがついた質問を見る

  • C#

    4756questions

    C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。