非同期ダウンロードについて への返答
投稿で使用できる特殊コードの説明。(別タブで開きます。)
以下の返答は逆順(新しい順)に並んでいます。
投稿者 YuO  (社会人)
投稿日時
2011/10/17 18:40:46
> コードにLINQが使われていますが、これはどんな理由で使用しているのでしょうか。
楽をするためです。
# どちらもList1のCheckedItemsの各要素をStringにキャストしたものを配列にしている。
楽をするためです。
Dim checkedItems As New List(Of String)
For Each s As String In List1.CheckedItems
checkedItems.Add(s)
Next
backgroundWorker1.RunWorkerAsync(checkedItems.ToArray())
に比べて,backgroundWorker1.RunWorkerAsync(List1.CheckedItems.Cast(Of String)().ToArray())
だと,「慣れている人にとっては」楽に書けますし,やっていることが明確になります。# どちらもList1のCheckedItemsの各要素をStringにキャストしたものを配列にしている。
投稿者 パル36  (中学生)
投稿日時
2011/10/17 17:45:43
有難うございます。
>基本的に,UIに対して「データの取得」「データの設定」といった行為は,全部できないと考えてください。
>例えば,
>> For Each checked As String In List1.CheckedItems
>は,List1.CheckedItems.GetEnumerator()という「UIデータの取得行為」を行っているので,例外が発生し>ます。
>もちろん,MessageBoxもだめですし,ラベルへのTextの設定もだめです。
わかりました。ほとんど操作できないのですね。
>LINQ使ってCheckedItemsをString()に変換して渡す
コードにLINQが使われていますが、これはどんな理由で使用しているのでしょうか。
これも、ウァーカーのせいでしょうか。
LINQについては、言葉しか知らなかったです。。。
ダイレクトキャストやユージングの例までありがとうございます。
Usingは便利そうですね。これなら、Disposeをする必要がなく感激しました!!
まだ、読んでいるだけなので実際に試してみたいと思います。
参考サイトもありがとうございます。
マイクロソフトさんが書いているので、すごいですね。
#少し理解まで時間が掛かりそうなので時間をください。
>基本的に,UIに対して「データの取得」「データの設定」といった行為は,全部できないと考えてください。
>例えば,
>> For Each checked As String In List1.CheckedItems
>は,List1.CheckedItems.GetEnumerator()という「UIデータの取得行為」を行っているので,例外が発生し>ます。
>もちろん,MessageBoxもだめですし,ラベルへのTextの設定もだめです。
わかりました。ほとんど操作できないのですね。
>LINQ使ってCheckedItemsをString()に変換して渡す
コードにLINQが使われていますが、これはどんな理由で使用しているのでしょうか。
これも、ウァーカーのせいでしょうか。
LINQについては、言葉しか知らなかったです。。。
ダイレクトキャストやユージングの例までありがとうございます。
Usingは便利そうですね。これなら、Disposeをする必要がなく感激しました!!
まだ、読んでいるだけなので実際に試してみたいと思います。
参考サイトもありがとうございます。
マイクロソフトさんが書いているので、すごいですね。
#少し理解まで時間が掛かりそうなので時間をください。
投稿者 YuO  (社会人)
投稿日時
2011/10/17 16:15:01
> >ただし,UIへのアクセスができないので,その部分を考えて処理する必要があります。
> そのアクセスというのはどういうものなのでしょうか。
基本的に,UIに対して「データの取得」「データの設定」といった行為は,全部できないと考えてください。
例えば,
> For Each checked As String In List1.CheckedItems
は,List1.CheckedItems.GetEnumerator()という「UIデータの取得行為」を行っているので,例外が発生します。
もちろん,MessageBoxもだめですし,ラベルへのTextの設定もだめです。
取得に関しては先に取得しておき,設定や表示に関してはReportProgressイベントやRunWorkerCompletedイベントで行います。
個人的には,
Blog: マルチスレッド Windows フォームアプリケーションの開発 - とあるコンサルタントのつぶやき - Site Home - MSDN Blogs
http://blogs.msdn.com/b/nakama/archive/2009/03/30/windows.aspx
の,一連の記事を読むとよいと思っています (コードはC#で書かれていますが)。
最低でも,
Blog: Part 4. Visual Studio によるマルチスレッドアプリの開発 - とあるコンサルタントのつぶやき - Site Home - MSDN Blogs
http://blogs.msdn.com/b/nakama/archive/2009/04/09/part-4-visual-studio.aspx
にはBackgroundWorkerについて色々書いてあるので読むべきです。
# 日本マイクロソフトの赤間さんの記事です。
> そのアクセスというのはどういうものなのでしょうか。
基本的に,UIに対して「データの取得」「データの設定」といった行為は,全部できないと考えてください。
例えば,
> For Each checked As String In List1.CheckedItems
は,List1.CheckedItems.GetEnumerator()という「UIデータの取得行為」を行っているので,例外が発生します。
もちろん,MessageBoxもだめですし,ラベルへのTextの設定もだめです。
取得に関しては先に取得しておき,設定や表示に関してはReportProgressイベントやRunWorkerCompletedイベントで行います。
Private Sub button1_Click (sender As Object, e As EventArgs) Handles button.Click
' ボタンのハンドラ = UIスレッド
button1.Enabled = False
lbl.Text = "状況 : ダウンロードを開始します..."
If MsgBox( "ダウンロードを開始しますか。", MsgBoxStyle.YesNo Or MsgBoxStyle.Question, "ダウンロードを開始しようとしています。") <> MsgBoxResult.Yes Then
lbl.Text = "状況 : ダウンロードを中止しました。"
button1.Enabled = True
Return
End If
My.Computer.Audio.PlaySystemSound(System.Media.SystemSounds.Question)
backgroundWorker1.RunWorkerAsync(List1.CheckedItems.Cast(Of String)().ToArray()) ' LINQ使ってCheckedItemsをString()に変換して渡す
End Sub
Private Sub backgroundWorker1_DoWork (sender As Object, e As DoWorkEventArgs) Handles backgroundWorker1.DoWork
' DoWorkイベント = バックグラウンドスレッド
Dim checkedItems As String() = DirectCast(e.Argument, String())
Dim numCount As Integer = 0 '連番ファイルカウント
Using (wc As New WebClient())
For Each checked As String In checkedItems
Dim savePath As String = My.Application.Info.DirectoryPath '保存先
Dim saveFileName = Path.Combine(savePath, "Image" & numCount & Path.GetExtension(checked))
' 同期ダウンロード
wc.DownloadFile(checked, saveFileName)
numCount += 1
Next
End Using
End sub
Private Sub backgroundWorker1_RunWorkerCompleted (sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker.RunWorkerCompleted
' 終了ハンドラ = UIスレッド
If e.Error IsNot Nothing Then
lbl.Text = e.Error.Message
txtLog.Text &= vbCrLf & "・エラーが発生しました。" & vbCrLf & "エラーメッセージ:" & e.Error.Message & "スタックトレース:" & e.Error.StackTrace
TabControl.SelectTab(1)
txtLog.Select(0, 0)
Else
lbl.Text = "状況 : ダウンロードが完了しました。"
End If
button1.Enabled = True
End Sub
個人的には,
Blog: マルチスレッド Windows フォームアプリケーションの開発 - とあるコンサルタントのつぶやき - Site Home - MSDN Blogs
http://blogs.msdn.com/b/nakama/archive/2009/03/30/windows.aspx
の,一連の記事を読むとよいと思っています (コードはC#で書かれていますが)。
最低でも,
Blog: Part 4. Visual Studio によるマルチスレッドアプリの開発 - とあるコンサルタントのつぶやき - Site Home - MSDN Blogs
http://blogs.msdn.com/b/nakama/archive/2009/04/09/part-4-visual-studio.aspx
にはBackgroundWorkerについて色々書いてあるので読むべきです。
# 日本マイクロソフトの赤間さんの記事です。
投稿者 パル36  (中学生)
投稿日時
2011/10/17 15:29:04
YuOさん返信有難うございます。
BackgroundWorker
とても参考になります!
>ただし,UIへのアクセスができないので,その部分を考えて処理する必要があります。
そのアクセスというのはどういうものなのでしょうか。
MSDNの説明には、
「進行状況の更新の通知を受け取るには、ProgressChanged イベントを処理します。操作の完了時に通知を受け取るには、RunWorkerCompleted イベントを処理します。」
と書いてありますが、これとは違うのですか。
バックグラウンドウォーカーで探していたら、
http://msdn.microsoft.com/ja-jp/library/ms229675.aspx
「方法 : バックグラウンドでファイルをダウンロードする」がありました!
とりあえず、読んでみます!
他に何かあったらお願いします。
BackgroundWorker
とても参考になります!
>ただし,UIへのアクセスができないので,その部分を考えて処理する必要があります。
そのアクセスというのはどういうものなのでしょうか。
MSDNの説明には、
「進行状況の更新の通知を受け取るには、ProgressChanged イベントを処理します。操作の完了時に通知を受け取るには、RunWorkerCompleted イベントを処理します。」
と書いてありますが、これとは違うのですか。
バックグラウンドウォーカーで探していたら、
http://msdn.microsoft.com/ja-jp/library/ms229675.aspx
「方法 : バックグラウンドでファイルをダウンロードする」がありました!
とりあえず、読んでみます!
他に何かあったらお願いします。
投稿者 YuO  (社会人)
投稿日時
2011/10/15 15:56:11
非同期ですから,DownloadFileAsyncを呼び出したら,ダウンロードが終了しなくてもそのスレッドの実行を継続します。
WebClient.DownloadFileAsyncはイベントベースの非同期I/Oなので,
ダウンロードが終了したらDownloadFileCompletedイベントが発生します。
MSDN: WebClient.DownloadFileAsync メソッド (Uri, String) (System.Net)
http://msdn.microsoft.com/ja-jp/library/ms144196.aspx
たぶん,BackgroundWorker使ってDownloadFileでダウンロードしていくのが簡単だと思います。
ただし,UIへのアクセスができないので,その部分を考えて処理する必要があります。
イベント処理するとかTask.ContinueWithとかRxとかありますが,
結局BackgorundWorkerで内部は同期で書く方法で書くのが基本になると思います。
WebClient.DownloadFileAsyncはイベントベースの非同期I/Oなので,
ダウンロードが終了したらDownloadFileCompletedイベントが発生します。
MSDN: WebClient.DownloadFileAsync メソッド (Uri, String) (System.Net)
http://msdn.microsoft.com/ja-jp/library/ms144196.aspx
たぶん,BackgroundWorker使ってDownloadFileでダウンロードしていくのが簡単だと思います。
ただし,UIへのアクセスができないので,その部分を考えて処理する必要があります。
イベント処理するとかTask.ContinueWithとかRxとかありますが,
結局BackgorundWorkerで内部は同期で書く方法で書くのが基本になると思います。
投稿者 パル36  (中学生)
投稿日時
2011/10/15 11:29:19
こんにちは。毎回お世話になっています。
今回は、ダウンロードを非同期化させたいと思ってプログラムしたのですが、
うまくいきません。
解決策を教えていただけないでしょうか。
↓元のコード
New Uri(checked)は、下のコードでブラウザから画像URLを取得し、
リストボックスに表示させているアイテムです。
上記は動きますが、たくさんチェックされているとダウンロードの間、なにもできません。
なので、http://www.vbstation.net/tips/downloadfileasync.htm
を参照してやってみました。
しかし、複数チェックしてダウンロードすると
エラーメッセージ:WebClient は同時 I/O 操作をサポートしません。
スタックトレース:場所 System.Net.WebClient.ClearWebClientState()
場所 System.Net.WebClient.DownloadFileAsync(Uri address, String fileName, Object userToken)
場所 System.Net.WebClient.DownloadFileAsync(Uri address, String fileName)
とエラーが出てしまいます。
Disposeの位置を変えても変わりませんでした。また、インスタンスのスコープを変えて見ましたが変わりませんでした。
失敗したコードを載せます。
My.Computer.Network.DownloadFileではできたのに、DownloadFileAsyncではこのようなことはできないのでしょうか。
同時ダウンロードではなく、1つが完了したら次をダウンロードしたいです。
タイマーを使って、数秒後に確認しダウンロード中だったら待機させようとしましたができませんでした。
(どうすればいいかわかりませんでした。)
自分では解決策が見つかりませんでした。
スレッドを別にしたりしないといけないのでしょうか。
よろしくお願いします。
なにかコードが不明な箇所があればいってください。(一部書き換えたので)
今回は、ダウンロードを非同期化させたいと思ってプログラムしたのですが、
うまくいきません。
解決策を教えていただけないでしょうか。
↓元のコード
Try
Dim NumCount As Integer = 0 '連番ファイルカウント
lbl.Text = "状況 : ダウンロードを開始します..."
If MsgBox( "ダウンロードを開始しますか。", MsgBoxStyle.YesNo Or MsgBoxStyle.Question, "ダウンロードを開始しようとしています。") = MsgBoxResult.Yes Then
My.Computer.Audio.PlaySystemSound(System.Media.SystemSounds.Question)
For Each checked As String In List1.CheckedItems
Dim wc As New WebClient 'インスタンス
Dim SavePath As String = My.Application.Info.DirectoryPath '保存先
Dim Ext As String = Path.GetExtension(checked) 'URLから拡張子だけを取得
'保存ファイル名を連番にする
My.Computer.Network.DownloadFile(New Uri(checked), Path.Combine(SavePath, "Image" & NumCount & Ext), "", "", True, 30000, True, FileIO.UICancelOption.ThrowException)
NumCount = NumCount + 1
wc.Dispose()
Next
Else
lbl.Text = "状況 : ダウンロードを中止しました。"
Exit Sub
End If
lbl.Text = "状況 : ダウンロードが完了しました。"
Catch ex As Exception
lbl.Text = ex.Message
txtLog.Text += vbCrLf & "・エラーが発生しました。" & vbCrLf & "エラーメッセージ:" & ex.Message & "スタックトレース:" & ex.StackTrace
TabControl.SelectTab(1)
txtLog.Select(0, 0)
End Try
End Sub
New Uri(checked)は、下のコードでブラウザから画像URLを取得し、
リストボックスに表示させているアイテムです。
List1.Items.Clear()
List2.Items.Clear()
Dim Body As HtmlElement = BrowserMain.Document.Body
Dim Images As HtmlElementCollection = Body.GetElementsByTagName("img")
For Each Img As HtmlElement In Images
Dim src As String = Img.GetAttribute("src")
Dim ext As String = Path.GetExtension(src)
List1.Items.Add(src)
If Not List2.Items.Contains(ext) Then
List2.Items.Add(ext, True)
End If
Next
上記は動きますが、たくさんチェックされているとダウンロードの間、なにもできません。
なので、http://www.vbstation.net/tips/downloadfileasync.htm
を参照してやってみました。
しかし、複数チェックしてダウンロードすると
エラーメッセージ:WebClient は同時 I/O 操作をサポートしません。
スタックトレース:場所 System.Net.WebClient.ClearWebClientState()
場所 System.Net.WebClient.DownloadFileAsync(Uri address, String fileName, Object userToken)
場所 System.Net.WebClient.DownloadFileAsync(Uri address, String fileName)
とエラーが出てしまいます。
Disposeの位置を変えても変わりませんでした。また、インスタンスのスコープを変えて見ましたが変わりませんでした。
失敗したコードを載せます。
For Each checked As String In List1.CheckedItems
Dim WebDownload As New WebClient 'インスタンス
Dim SavePath As String=My.Application.Info.DirectoryPath '保存先
Dim Ext As String = Path.GetExtension(checked) 'URLから拡張子だけを取得
Dim URL As System.Uri = New Uri(checked)
'保存ファイル名を連番にする
_Download.DownloadFileAsync(URL, Path.Combine(SavePath, "Image" & NumCount & Ext))
NumCount = NumCount + 1
End If
WebDownload.Dispose()
Next
End If
My.Computer.Network.DownloadFileではできたのに、DownloadFileAsyncではこのようなことはできないのでしょうか。
同時ダウンロードではなく、1つが完了したら次をダウンロードしたいです。
タイマーを使って、数秒後に確認しダウンロード中だったら待機させようとしましたができませんでした。
(どうすればいいかわかりませんでした。)
自分では解決策が見つかりませんでした。
スレッドを別にしたりしないといけないのでしょうか。
よろしくお願いします。
なにかコードが不明な箇所があればいってください。(一部書き換えたので)
># どちらもList1のCheckedItemsの各要素をStringにキャストしたものを配列にしている。
確かに、同じだったらわかりやすいですね。
LINQの方法を使わさせていただきます。
そういえば、Usingのときに括弧をはずしないと機能しませんでした。
今回も、無事に解決しました
ありがとうございます!