DownloadDataAsync + Delegate C#はmainで機能しますが、クラス関数では機能しません
質問
このコードを実行しました。メインとして機能しましたが、クラスに置いても機能しません。どうして? WebClient.DownloadDataAsync( )このコンテキストのメソッド?
bc data == nullをクラッシュさせ、null例外をスローします:(。
public class Test2
{
public void func()
{
byte[] data = null;
WebClient client = new WebClient();
client.DownloadDataCompleted +=
delegate(object sender, DownloadDataCompletedEventArgs e)
{
data = e.Result;
};
Console.WriteLine("starting...");
client.DownloadDataAsync(new Uri("https://stackoverflow.com/questions/"));
while (client.IsBusy)
{
Console.WriteLine("\twaiting...");
Thread.Sleep(100);
}
Console.WriteLine("done. {0} bytes received;", data.Length);
}
}
//i tried calling on form_load and a button click
new Test2().func();
解決
このコードには、データフィールドに競合状態があります。 DownloadDataCompleted匿名デリゲートは、data.Length呼び出しとは異なるスレッドから呼び出され、DownloadDataCompletedが呼び出された時点で、IsBusyはfalseになります。データに最初にアクセスする2つのスレッド間の競合です。ダウンロードスレッドでデータが設定される前にメインスレッドがdata.Lengthを呼び出すと、null参照例外が発生します。 DownloadDataCompleted削除を強制して、データを設定する前にThread.Sleep()呼び出しを追加することで、常に競合を失うかどうかを確認するのは簡単です。
スレッドの状態は次のようになります。
Main Thread Download Thread client.IsBusy
Waiting.... downloading... true
leaves waiting loop calls delegate false
calls data.Length data = e.Result
どのスレッドが最後の行を最初に実行するかを知る方法はありません。マルチプロセッサマシンでは、これらの両方を同時に実行できます。
これはすべてタイミングに基づいているため、動作することもあれば失敗することもあります。複数のスレッドがアクセスするすべてのデータに対して何らかの同期(ロック)が必要です。
他のヒント
winformのスレッドモデル( shf301
が指摘したように)のために、私は自分に合ったコードを修正しました。
private void button1_Click(object sender, EventArgs e)
{
func();
}
void func()
{
WebClient client = new WebClient();
byte[] data = null;
long rcv = 0; //last number of bytes received
//check data received for progress
client.DownloadProgressChanged += delegate(object sender, DownloadProgressChangedEventArgs e)
{
if (e.BytesReceived - rcv > 1000)
{
Console.WriteLine("\tBytes Received: " + e.BytesReceived.ToString());
rcv = e.BytesReceived;
}
//else don't report
Thread.Sleep(1);
};
client.DownloadDataCompleted +=
delegate(object sender, DownloadDataCompletedEventArgs e)
{
data = e.Result;
Console.WriteLine("done. {0} bytes received;", data.Length);
};
Console.WriteLine("starting...");
//fire and forget
client.DownloadDataAsync(new Uri("http://stackoverflow.com/questions/"));
}
出力があります:
starting...
Bytes Received: 8192
Bytes Received: 11944
Bytes Received: 15696
Bytes Received: 20136
Bytes Received: 24232
Bytes Received: 28040
Bytes Received: 32424
Bytes Received: 36176
Bytes Received: 40616
Bytes Received: 44712
Bytes Received: 48269
done. 48269 bytes received;
それは私のために動作しますか?
C:\TEMP\ConsoleApplication5\bin\Debug>ConsoleApplication5.exe
starting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
done. 48178 bytes received;
結果がループで待機する場合、非同期メソッドを使用する意味は何ですか?同期バージョンを使用するだけです:
public class Test2
{
public void func()
{
WebClient client = new WebClient();
byte[] data = client.DownloadData(new Uri("http://stackoverflow.com/questions/"));
Console.WriteLine("done. {0} bytes received;", data.Length);
}
}
//i tried calling on form_load and a button click
new Test2().func();