3

概念:指定されたURLからファイルをダウンロードするC#アプリを作成しています。テキストボックス、URLの追加、ファイルのダウンロード、すべてのイベントは正しい方法で発生します。

このプログラムを再作成して、複数のファイルを1つずつダウンロードしようとしています。1つのURL/行を持つテキストボックスがあります。解析は正しく行われます。テキストボックスに配置された文字列配列にすべてのリンクがあります。次に、非同期のダウンロードを開始し、1つずつダウンロードするようにしたかったので、現在のURLのダウンロードが完了するまで次のURLに移動したくないので、foreachループでwhileループを作成しました。

問題は次のとおりです。無限ループに陥ります(以前にこの作業を行いましたが(idk how)、whileループにメッセージボックスを配置した場合(注:1分前に再試行しましたが、今回はうまくいきませんでした) )。

コードスニペットを表示します。

foreach (string url in urllist)
{
    isdonwloaded = false;
    string filename = url.Split('/').Last();
    label3.Text = filename;
    webclient.DownloadFileAsync(new Uri(url), @"C:\Users\Krisz" + @"\" + filename);

    while (!isdonwloaded) // this was the first idea, but with webclient.IsBusy it did the same thing
    {
        // MessageBox.Show(counter);
        Thread.Sleep(1000);
        label8.Text = "Download in progress...";
    }

    counter++;
    label8.Text = "Done!";
}

// Events:
webclient.DownloadProgressChanged += new DownloadProgressChangedEventHandler(webc_DownloadProgressChanged);
webclient.DownloadFileCompleted += new AsyncCompletedEventHandler(webc_DownloadFileCompleted);

// The DownloadFileCompleted event:
void webc_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
    label7.Text = String.Format("Files {0} / {1}", counter, arraylength(urllist));
    isdonwloaded = true;
}

私はこのスレッドを調べました:WebClient.DownloadFileAsync-一度に1つずつファイルをダウンロードしますが、どちらの方法でも機能させることができませんでした。(多分私は何かを誤解しましたか?)

誰かが私にいくつかのヒントを与えることができます、私は何を間違えましたか?私は実際にイベントを使用したことがなかったので、エラーが発生するのは時間の問題でした。少しでも助けていただければ幸いです。このプログラムは私にとって便利なものです。

4

3 に答える 3

9

OK、まず、コードが現在機能していない理由を理解してください。

オフィスに 2 人いるとします。どちらも「箱入り」です。彼らのワークフローは次のとおりです。受信トレイをチェックします。受信トレイにタスクがある場合は、タスクが完了するまでタスクを実行し、受信トレイをもう一度確認して繰り返します。

ワーカー 1 の受信トレイに、あなたの次のタスクは次のとおりであるというメッセージが届きます。

  • スイッチをオフにする
  • ラベルを「ダウンロード中」に変更します
  • ワーカー 2 にファイルをダウンロードするように指示します
  • スイッチがオンになっているかどうかを確認します。オンになっている場合は、ループから抜け出します。そうでない場合は、1 秒間スリープ状態になります。
  • 前のステップに戻る
  • ラベルを「完了」に変更
  • このタスクは現在終了しています

ワーカー 1 はスイッチをオフにし、次のタスクをワーカー 2 の受信トレイに入れます。

  • ファイルをダウンロードする
  • 作業員 1 にスイッチを入れるように伝える
  • このタスクは終了しました

ワーカー 1 は、スイッチがオンになっているかどうかを確認します。そうではないので、ワーカー 1 はスリープ状態になります。

ワーカー 2 はファイルをダウンロードし、ワーカー 1 の受信トレイに次のようなメッセージを入れます。

  • スイッチを入れる
  • このタスクは終了しました

これで、ワーカー 1 が永遠にスリープ状態になる理由がわかりましたね。そのスイッチを切り替えるのはワーカー 1 の仕事であり、ワーカー 1 は切り替えられるまでスリープしているため、スイッチが切り替えられることはありません。ワーカー 1 は、現在のタスクが完了するまで受信トレイを確認せず、スイッチがオンになるまで現在のタスクは終了しません。

これにより、解決策のアイデアが得られますが、それは良いものではありません。

この問題を解決するための安くて汚い、危険で賢明でない方法は、「Sleep」の代わりに「DoEvents」を使用することです。これにより、タスクが次のように変更されます。

  • スイッチをオフにする
  • ラベルを「ダウンロード中」に変更します
  • ワーカー 2 にファイルをダウンロードするように指示します
  • スイッチがオンになっているかどうかを確認します。オンになっている場合は、ループから抜け出します。そうでない場合は、受信トレイでメッセージを確認し、そこにあるものを何でも実行してください。
  • 前のステップに戻る
  • ラベルを「完了」に変更
  • このタスクは終了しました

これにより当面の問題は解決しますが、新たな問題が発生します。クリーンなワークフローはなくなりました。1 つのインボックス タスクが 2 番目のインボックス タスクを生成し、それがさらに 3 番目のインボックス タスクを生成する可能性があります。タスクは「再入可能」になる可能性があり、1 つのタスクがそれ自体の 2 番目のバージョンを開始することになります。このソリューションは洗練されておらず、デバッグが困難な状況に対応しています。理想的には、受信ボックスのタスクには、古いタスクがまだ処理されている間ではなく、古いタスクが完了した後に新しいタスクが開始されるというプロパティが必要です。

問題に対するより安価で汚い修正 (C# 5 を使用している場合) は、次を使用することです。

await Task.Delay(1000);

Sleepまたはの代わりにDoEvents。これにより、ワークフローに微妙な変更が加えられます。基本的には次のようになります。

  • スイッチをオフにする
  • ラベルを「ダウンロード中」に変更します
  • ワーカー 2 にファイルをダウンロードするように指示します
  • スイッチがオンになっているかどうかを確認する
  • オンになっている場合は、ラベルを「完了」に変更します。このタスクは終了しました。
  • そうでない場合は、ワーカー 3 に 1 秒でタスクを送信するように依頼します。このタスクは終了しました。

ワーカー 3 がワーカー 1 に新しいタスクを送信するように指示された場合、送信される新しいタスクは次のようになります。

  • スイッチがオンになっているかどうかを確認する
  • オンになっている場合は、ラベルを「完了」に変更します。このタスクは終了しました。
  • そうでない場合は、ワーカー 3 に 1 秒でタスクを送信するように依頼します。このタスクは終了しました。

それがワークフローを微妙に、しかし正しく変更する方法がわかりますか? ここで、ワーカー 1 はラベルをダウンロードに変更し、ワーカー 2 にメッセージを送信し、スイッチをチェックして、ワーカー 3 にメッセージを送信し、受信トレイに戻ります。ワーカー 2 はダウンロードを行い、メッセージをワーカー 1 に送信します。ワーカー 1 はスイッチを切り替えて、受信トレイに戻ります。ワーカー 3 はワーカー 1 にメッセージを送信します。ワーカー 1 はスイッチを確認し、ラベルを完了に変更して、受信トレイに戻ります。

これで、受信トレイで他のタスクを探すように指示されるタスクはなくなりました。各受信トレイ タスクは順番に処理されます。後から到着するタスクは、先に到着するタスクが終了した後に常に開始されます。

ただし、最善の解決策は、DownloadClientAsyncそれ自体が待機可能なタスクを返すことができるバージョンを持つことです。残念ながら、これは void を返します。待機可能な を返す DownloadClientAsync の特別なバージョンの構築はTask、演習として残されています。このようなヘルパー メソッドがあれば、コードは簡単になります。あなたはawaitその仕事だけです。

于 2013-03-07T16:33:33.197 に答える
2

イベント サブスクリプション コードを現在の行から次の行に移動しようとしましたか:

webclient.DownloadFileAsync(new Uri(url), @"C:\Users\Krisz" + @"\" + filename);

  webclient.DownloadFileCompleted -= new AsyncCompletedEventHandler(webc_DownloadFileCompleted); 
//Also it's a good practice to unsubscribe to event once after we are out-of-scope.

 webclient.DownloadFileCompleted += new AsyncCompletedEventHandler(webc_DownloadFileCompleted);

私がこれを行ったとき、残りはうまくいきました。それ以外の場合は、あなたが言ったように画面が空白でした。

于 2013-03-07T16:42:52.853 に答える
1

これは、CompletedEventHandler がメイン スレッドで実行される BackgroundWorker クラスに非常に似ていると思います。(現時点でこれを確認する結果は見つかりません。)

これは、メイン ループが中断されないことを意味します。webc_DownloadFileCompleted が発生する可能性がある前に、メイン URL ループを終了して UI に戻る必要があります。

考えられる修正の 1 つは、最初の URL で単一の Web クライアント非同期ダウンロードを実行してから、メイン UI に戻ることです。webc_DownloadFileCompleted 関数は、次の非同期ダウンロード呼び出しを再発行できます。

于 2013-03-07T16:41:27.817 に答える