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

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

新規登録して質問してみよう
ただいま回答率
85.50%
C

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

C#

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

アルゴリズム

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

Q&A

解決済

5回答

18030閲覧

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

Rio12882

総合スコア27

C

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

C#

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

アルゴリズム

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

0グッド

0クリップ

投稿2016/11/01 07:28

現在、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個で約一分半ほどです。

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

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答5

0

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が固まることもありません。

C#

1private async void run() 2{ 3 // 再突入抑止のためボタン無効化(ボタン押下でrunメソッドが呼ばれると仮定) 4 button1.Enabled = false; 5 6 for(int i = 0; i < 1000; i++) 7 { 8 await Task.Run(() => 9 { 10 // ファイル1個作成 11 }); 12 progressBar1.Value = i + 1; 13 } 14 MessageBox.Show("完了しました。"); 15 16 // ボタン有効化 17 button1.Enabled = true; 18}

投稿2016/11/01 13:58

編集2016/11/02 02:23
catsforepaw

総合スコア5938

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ベストアンサー

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

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

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

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

csharp

1public async void MakeCsvAsync() 2{ 3 await Task.Run(() => 4 { 5 //ファイルの作成 6 }).ConfigureAwait(false); // <- 7 8......

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

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

投稿2016/11/02 02:41

tamoto

総合スコア4103

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

catsforepaw

2016/11/02 03:59

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

2016/11/02 04:26

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

2016/11/02 04:53

async/awaitを使う意義には、非同期処理を同期的に記述することで直感的なコードが書けるということと、UIスレッドで実行されることで排他制御が不要になりフォームのメンバ変数やUI部品へのアクセスが容易になるという点があると考えています。ConfigureAwait(false)とやると、特に後者の恩恵が受けられなくなってしまうため、そもそもasync/awaitとする必要性がなくなってしまいます。 > 今回のデッドロックのような分かりづらい不具合が発生することのほうが問題だと思っています。 今回のは判りやすい部類ですよ。むしろ、別スレッドにすることで必要な同期制御・排他制御を行わなかったことにより発生する問題の方が難易度が高いと思います。安易なConfigureAwait(false)はその危険性を高めます。
tamoto

2016/11/02 05: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が「特定のスレッドに強く紐ついている状況」になっているなら、それは非同期メソッドの設計が間違っていると思われます。
catsforepaw

2016/11/02 06:06

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

2016/11/02 06:28

> 同期制御や排他制御はConfigureAwaitの状態に関係なく必要だと思うのですが。 asyncメソッドがUIスレッドで動いている限りは、UIスレッドとの同期・排他制御は不要ですよ。Task.Runの中のコードは当然必要ですが。
tamoto

2016/11/02 06: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イベントハンドラに使う時の記述が面倒になるデメリットと比べて、どちらが大きいと感じるかは人によりますが。
catsforepaw

2016/11/02 06:57 編集

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

2016/11/02 07:27

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

2016/11/02 08:11

> async/awaitとConfigureAwaitが同時じゃないというのは初耳でした。 すみません。ちょっと気になって再度確認してみたところ、async/awaitとConfigureAwaitは同時期ですね(C#5.0と.NET Framework 4.5)。 async/awaitは.NET Framework 4でも使えるので勘違いしてしまいました。 > 結局、UIイベントハンドラとして使うことを前提にデザインされた構文であり、そういうものだと納得するしかないのでしょうね。 そうかもしれませんね。実際、コンソールアプリでasync/awaitを使うとフォームアプリと挙動が変わって使いづらいですし。
guest

0

こんにちは。

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

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

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

投稿2016/11/01 08:07

Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

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 08:05

退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

KSwordOfHaste

2016/11/01 08:30

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

0

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

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

投稿2016/11/01 07:59

編集2016/11/01 08:05
KSwordOfHaste

総合スコア18392

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問