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

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

ただいまの
回答率

90.03%

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

解決済

回答 5

投稿

  • 評価
  • クリップ 0
  • VIEW 8,149

Rio12882

score 25

現在、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 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)を用意しているようです。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

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

  • ただいまの回答率 90.03%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる