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

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

ただいまの
回答率

90.51%

  • C#

    9054questions

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

  • C

    4539questions

    C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

  • アルゴリズム

    500questions

    アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。

ファイルを作成中のフォルダの進行度をプログレスバーで表示する

解決済

回答 5

投稿

  • 評価
  • クリップ 0
  • VIEW 6,472

Rio12882

score 19

現在、C#でプログラムを作成しています。
・あるフォルダに非同期でファイルを1000個作成するプログラムを作成する
・そのフォルダのすでに作成されているフォルダの数を取得して完了率をプログレスバーに表示する
上記のプログラムを作っているのですが実行すると、あるところでデッドロックが発生してしまいます。
デッドロックを起こすことなく、進捗率を表示することのできる方法があれば知りたいのですがどなたかご教示いただけないでしょうか。
よろしくお願いいたします。

public async void MakeCsvAsync()
        {
            await Task.Run(() =>
            {
        //ファイルの作成
            });
            loop_progressbar = false;
            MessageBox.Show("完了しました。");
        }
        private void run()
        {
            MakeCsvAsync();
            loop_progressbar = true;
            try
            {
               System.Threading.Thread.Sleep(5000);
               while(loop_progressbar)
               {
                  int num = Directory.GetFiles(dir, "*", SearchOption.TopDirectoryOnly).Length;
                  System.Threading.Thread.Sleep(2000);
                  progressBar1.Value = num / 1000;
               }
       }
             catch(Exception e)
             {
               MessageBox.Show(e.Message);
             }
         }

ファイルの作成自体は、1000個で約一分半ほどです。

よろしくお願いいたします。

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 5

+3

async/awaitの挙動を理解できていないのが原因ですね。少々ややこしいです。

MakeCsvAsyncメソッドでawait Task.Runを実行すると、別スレッドでその中身を実行開始してすぐに一旦UIスレッドに制御を返します。そして、別スレッドでの処理終了後にUIスレッドでその続きが実行されます。
ところが、MakeCsvAsyncメソッドはrunメソッド内から呼び出されており、runメソッドはloop_progressbar == trueの間ループしてメソッドから抜けないのでUIスレッドを占有した状態となり、awaitの続きを実行することができません。結果、loop_progressbarがいつまでたってもfalseにならず、runメソッド内で永久ループになってしまっています。

とりあえずの回避策としては、loop_progressbar = false;をTask.Runの中の処理の最後に書いてみてください。
あと、念のためrunメソッドのloop_progressbar = true;MakeCsvAsync();の前に書いてください。

asyncメソッド内のawait後の処理は、フォームが何もメソッドを実行していない状態の時に実行されるという点に注意してください。


追記

runメソッド内で時間のかかる処理(別スレッドの処理待ち)をするのは得策ではありません。その間、フォームに対するあらゆる操作ができなくなりますし、プログレスバーのハイライトのアニメーションが見られなくなります。

こんな感じに書いた方がシンプルで判りやすいですし、UIが固まることもありません。

private async void run()
{
    // 再突入抑止のためボタン無効化(ボタン押下でrunメソッドが呼ばれると仮定)
    button1.Enabled = false;

    for(int i = 0; i < 1000; i++)
    {
        await Task.Run(() =>
            {
                // ファイル1個作成
            });
        progressBar1.Value = i + 1;
    }
    MessageBox.Show("完了しました。");

    // ボタン有効化
    button1.Enabled = true;
}

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

checkベストアンサー

0

こんにちは。
非同期の問題については既に他の方が詳しく回答しているので、

あるところでデッドロックが発生してしまいます。

の原因とその回避方法についてだけ回答してみます。

デッドロックは以下の箇所の変更のみで回避できます(はずです)。

public async void MakeCsvAsync()
{
    await Task.Run(() =>
    {
        //ファイルの作成
    }).ConfigureAwait(false); // <-

......

この問題の詳細については、以下の情報を参照して下さい。
http://qwerty2501.hatenablog.com/entry/2014/04/24/235849
同様の事象について、発生原因とその回避方法が簡潔に解説されています。Waitメソッドによるスレッド専有がwhileループによる専有に変化しているだけです。

async/awaitは他にも細かいハマりどころが多いので、まずはこの問題の発生原因をきっちり理解しておくことをオススメします。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2016/11/02 12:59

    `ConfigureAwait(false)`を使うと意図せぬ問題が起こるので、私はあまりお勧めしません。
    ご質問のコードにそのまま適用すると、完了時のメッセージボックスが別スレッドで実行されることになり、挙動が変わってしまいますよ。

    キャンセル

  • 2016/11/02 13:26

    コメントありがとうございます。
    この質問のコードでは、MessageBoxの呼び出しがMakeCsvAsyncメソッド内に書かれているため、むしろ「別スレッドからメッセージボックスを呼ぶ」ことが本来意図したものであると読み取りました。
    まあ、元の質問のコード自体が非同期処理としては微妙な処理なので、根本的に修正する場合はcatsforepawさんの回答のとおりのコードになるため、そもそもこの質問自体が意味をなさないことになってしまいますね。
    また、自分がTaskを扱うときは、明示的にContextを掴んでおく必要が無い場面では全てConfigureAwait(false)を付けてしまったほうが良いと思っているタイプです。具体的には、イベントハンドラから直接呼び出す先の非同期メソッド以外にはほぼ全てConfigureAwait(false)が付きます。むしろ、デフォルト設定がコンテキストを掴むようになっていることで、今回のデッドロックのような分かりづらい不具合が発生することのほうが問題だと思っています。

    キャンセル

  • 2016/11/02 13:53

    async/awaitを使う意義には、非同期処理を同期的に記述することで直感的なコードが書けるということと、UIスレッドで実行されることで排他制御が不要になりフォームのメンバ変数やUI部品へのアクセスが容易になるという点があると考えています。ConfigureAwait(false)とやると、特に後者の恩恵が受けられなくなってしまうため、そもそもasync/awaitとする必要性がなくなってしまいます。

    > 今回のデッドロックのような分かりづらい不具合が発生することのほうが問題だと思っています。

    今回のは判りやすい部類ですよ。むしろ、別スレッドにすることで必要な同期制御・排他制御を行わなかったことにより発生する問題の方が難易度が高いと思います。安易なConfigureAwait(false)はその危険性を高めます。

    キャンセル

  • 2016/11/02 14:49

    あっなるほど、自分はasync/awaitを「Taskを記述する際の便利な糖衣構文」としてしか考えてなかったので、若干認識の齟齬があったようです。
    「非同期処理を同期的に記述」と、「UIへのアクセスが容易」という2点については、まさに前のコメントで例外扱いとした「(UI上の)イベントハンドラから直接呼び出す先の非同期メソッド」のことで、これは当然ConfigureAwait(false)は使いませんね。仰る通り、async/awaitの利点が失われてしまいます。
    async/await、Taskクラスの利点は、「スレッドという実体に縛られずに非同期処理を記述でき、それを容易に合成できること」だと思っているので、UIスレッドという単一のコンテキストが存在して、そのスレッドに縛られてしまうUI処理というものが、実際Taskとはあまり相性が良くないんですよね。そのために、async/awaitはコンテキストを保存するように設計されたわけですが……。

    申し訳ないですが、コメントの「必要な同期制御・排他制御を行わなかったことにより発生する問題」というものと、ConfigureAwait(false)がどう関係するのか、ちょっと読み取れませんでした。同期制御や排他制御はやっかいな話ですが、それが「ConfigureAwait(false)にすることによって不具合を起こす」場面というものが想像できませんでした。同期制御や排他制御はConfigureAwaitの状態に関係なく必要だと思うのですが。または、もしも、Taskが「特定のスレッドに強く紐ついている状況」になっているなら、それは非同期メソッドの設計が間違っていると思われます。

    キャンセル

  • 2016/11/02 15:06

    > コメントの「必要な同期制御・排他制御を行わなかったことにより発生する問題」というものと、ConfigureAwait(false)がどう関係するのか、

    asyncメソッドは通常のメソッドと同様にUIスレッドで動くものと期待するので、通常のメソッドと同じようにコードを書くと思います(それがメリットでもあります)。ところが、ConfigureAwait(false)によりasyncメソッド自体が別スレッドで動くことになれば、場合によってはUIスレッドとの同期・排他制御などが必要になります。最初から非同期で動かすつもりならそれなりに注意深くコードを書くでしょうけど、async/awaitを使っているという安心感から、注意がおろそかになる危険性がある、ということです。

    キャンセル

  • 2016/11/02 15:28

    > 同期制御や排他制御はConfigureAwaitの状態に関係なく必要だと思うのですが。

    asyncメソッドがUIスレッドで動いている限りは、UIスレッドとの同期・排他制御は不要ですよ。Task.Runの中のコードは当然必要ですが。

    キャンセル

  • 2016/11/02 15:44

    なるほど、理解しました。同じ懸念を全く逆の方法で対処しようとしていたということですね。

    「asyncメソッドは通常のメソッドと同様にUIスレッドで動くものと期待する」
    ここが認識の分かれ目でした。自分は「Taskは記述した処理がどのスレッドで実行されるかを意識しなくても良いもの」と捉えていたため、普段は全てConfigureAwait(false)を使い、UIへのアクセスを行うメソッド等、「実行コンテキスト情報の保持が必要なときだけConfigureAwaitを使わない」、という使い方を推奨していました。そうすれば、UIアクセスなどの「コンテキスト情報が必要な場面」が自然に浮かび上がるからです。

    「ConfigureAwait(false)によりasyncメソッド自体が別スレッドで動くことになれば、場合によってはUIスレッドとの同期・排他制御などが必要になります」
    これは違います。「UIスレッドへのアクセスや同期が必要なときだけConfigureAwait(false)を付けない」という設計なので、「UIスレッドへのアクセスが必要なのにConfigureAwait(false)を付けた」というコーディングミス時の話になっちゃってます。通常この状況は発生しません。

    「async/awaitを使っているという安心感から、注意がおろそかになる危険性がある」
    まさにこれの通りで、だからこそ、デフォルトではConfigureAwaitはfalseであるべきだったと思うのです。コンテキスト情報が必要になってからConfigureAwait(true)でコンテキストを保持するように明示的に記述するほうが、Taskとしては自然な振る舞いだと感じます。この利点が、UIイベントハンドラに使う時の記述が面倒になるデメリットと比べて、どちらが大きいと感じるかは人によりますが。

    キャンセル

  • 2016/11/02 15:56 編集

    考え方の違いですね。
    ちなみに、async/awaitが登場したときはまだConfigureAwaitは実装されていませんでした。ですので、デフォルトでConfigureAwait(false)というわけにはいかないのでしょう。

    キャンセル

  • 2016/11/02 16:27

    C#5.0以前のTaskクラスがかなり貧弱だったのは知っていましたが、async/awaitとConfigureAwaitが同時じゃないというのは初耳でした。
    結局、UIイベントハンドラとして使うことを前提にデザインされた構文であり、そういうものだと納得するしかないのでしょうね。

    キャンセル

  • 2016/11/02 17:11

    > async/awaitとConfigureAwaitが同時じゃないというのは初耳でした。

    すみません。ちょっと気になって再度確認してみたところ、async/awaitとConfigureAwaitは同時期ですね(C#5.0と.NET Framework 4.5)。
    async/awaitは.NET Framework 4でも使えるので勘違いしてしまいました。

    > 結局、UIイベントハンドラとして使うことを前提にデザインされた構文であり、そういうものだと納得するしかないのでしょうね。

    そうかもしれませんね。実際、コンソールアプリでasync/awaitを使うとフォームアプリと挙動が変わって使いづらいですし。

    キャンセル

0

UIコンポーネントへのアクセス(※)をメインスレッド以外から行ってはいけません。まずそういった非同期処理の基本を学ぶ必要があろうかと思います。非同期処理をしつつプログレスバーなどのUIコンポーネントを更新するやりかたについてはあちこちに考え方やサンプルがあると思いますのでそれを参考にするとよいです。やりかたは一通りではありません。Form/WPFでも若干違うと思います。
「window form 非同期処理」などで検索すると情報が得られると思います。

※: この場合はプログレスバーのプロパティーアクセスです

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

0

今回の質問とは直接関係ない余計はお世話的なレスですみませんが、どうしても気になったもので・・・

Exception をキャッチするのは止めた方がいいです。理由は以下の記事を見てください。

NETの例外処理 Part.1
https://blogs.msdn.microsoft.com/nakama/2008/12/29/net-part-1/

.NETの例外処理 Part.2
https://blogs.msdn.microsoft.com/nakama/2009/01/02/net-part-2/

.NET 4 からは破損状態例外は catch できなくなっているそうですが、「それでも Catch (Exception e) を使用するのはよくない」ということについては以下の記事を見てください。

破損状態例外を処理する
https://msdn.microsoft.com/ja-jp/magazine/dd419661.aspx

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2016/11/01 17:30

    む・・・そんな例外があるんですね!破損状態をハンドリングさせるオプションがあること自体が自分には謎です...意味がないような...

    キャンセル

0

こんにちは。

Task.Run()の中でコントロールを描画しているとおかしなことになりますが、そのような一般的な禁止事項はされてないと仮定して回答してみます。

書かれている処理を普通に実装した時、特にデッドロックしそうな印象はありません。
恐らくWindows APIを直接使っていればデッドロックしないと思います。
2~3年前の経験ですが、C#のファイル・アクセスは単にWindows APIへパススルーしているだけでなく、何か謎の処理をしていることがありました。その時は単にテキスト・ファイルへアペンドしているだけにも関わらずファイルが大きくなると異常に遅くなる現象でした。Windows APIを直接使うようにしてあっさり解決しました。他にも再現性のない不可解な動作も幾度か経験し、それ以来C#のファイル処理はどうも信用できません。
ですので、C#のファイル・アクセスを使わないでWindows APIを直接呼び出すことで解決する可能性はあると思います。

しかし、結構手間ですね。代わりにTask.Run()から進捗を示す数値をメイン・スレッドを送ることも考えられます。
ちょっと古いですが、時間のかかる処理の進行状況を表示するで紹介されているBackgroundWorkerは、正にこの目的のための仕組み(ProgressChanged)を用意しているようです。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

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

  • C#

    9054questions

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

  • C

    4539questions

    C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

  • アルゴリズム

    500questions

    アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。