次のBackgroundWorker
ように、メインの UI スレッドから呼び出す があります。
MainWindow で宣言しますBackgroundWorker
private BackgroundWorker backgroundWorkerRefreshFromWeb = new BackgroundWorker();
そして、コンストラクターで次のように設定しました。
backgroundWorkerRefreshFromWeb.WorkerReportsProgress = true;
backgroundWorkerRefreshFromWeb.WorkerSupportsCancellation = true;
backgroundWorkerRefreshFromWeb.DoWork +=
new DoWorkEventHandler(backgroundWorkerRefreshFromWeb_DoWork);
backgroundWorkerRefreshFromWeb.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(backgroundWorkerRefreshFromWeb_RunWorkerCompleted);
backgroundWorkerRefreshFromWeb.ProgressChanged +=
new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
コンストラクターの最後で、次のメソッドを呼び出して途中で開始します。
private void RefreshWebDataTimer(Object state)
{
if (!backgroundWorkerRefreshFromWeb.IsBusy && !backgroundWorkerLoadFromDB.IsBusy)
{
System.Diagnostics.Debug.Print("Refresh Timer Started at {0}", DateTime.Now);
backgroundWorkerRefreshFromWeb.RunWorkerAsync(nfl);
}
}
DoWork
イベント ハンドラーは、別のプロジェクト (DLL) からメソッドを呼び出します。そのメソッドには、プロセス作業を行うために複数のスレッドを呼び出すパターンがあります。これらのスレッドの 1 つがエラーをスローすると、アプリケーションがクラッシュし、イベントBackgroundWorker
でキャッチされません。RunWorkerCompleted
パターンは複雑 (おそらく過度に複雑) ですが、次のようになります。
DoWork イベント ハンドラーが呼び出すメソッドで、次のようにラッパーで一連の「サブワーカー」スレッドを作成します...次に、すべてのスレッドが処理を終了するのを待ってから次に進みます。
private static void GetRoster(Nfl teams, ref ManualResetEvent[] mre, ref int index)
{
mre = new ManualResetEvent[Dictionary.NUMBER_OF_NFL_TEAMS];
ParseDataAsyncWrapper[] dw = new ParseDataAsyncWrapper[Dictionary.NUMBER_OF_NFL_TEAMS];
index = 0;
foreach (NflTeam team in teams)
{
//Get Roster and create players
mre[index] = new ManualResetEvent(false);
dw[index] = new ParseDataAsyncWrapper(team, mre[index]);
ThreadPool.QueueUserWorkItem(new WaitCallback(dw[index].RosterCallbackAsync), index++);//Don't fully understand this
Thread.Sleep(wait.Next(Dictionary.THREAD_WAIT_MS));
}
foreach (ManualResetEvent re in mre) { if (re != null) { re.WaitOne(); } } //Wait for all threads to finish
mre = null; //allow to be disposed
dw = null;
}
各スレッドのコールバックを使用して Web ページを取得し、そのページを処理します。
internal async void RosterCallbackAsync(object State)
{
if (Thread.CurrentThread.Name == null) { Thread.CurrentThread.Name = string.Format("Roster{0}", State); }
WebPage = await Html.WebClientRetryAsync(Dictionary.ROSTER_WEBPAGE.Replace(Dictionary.CITY_REPLACE_STR, this.Team.CityAbr));
Html.ParseRoster(WebPage, Team);
DoneEvent.Set();
}
次に、Html.ParseRoster で例外をスローしていますが、キャッチされていません。これは とは別のスレッドにありBackgroundWorker
ます。BackgroundWorker
なぜキャッチされないのかわかりません。RunWorkerCompleted
先に進む前にすべてのスレッドが終了するのを待っているので、ここで完了する前にイベントが実行されるとは思いません。
Application.DispatcherUnhandledException イベントのヘルプ ページを見たところ、次のように記載されています。
次のことを行うコードを記述する必要があります。 バックグラウンド スレッドで例外を処理します。これらの例外をメイン UI スレッドにディスパッチします。DispatcherUnhandledException が発生するように、それらを処理せずにメイン UI スレッドで再スローします。
私の質問は 1) 例外が捕捉されないのはなぜですか? 使用する必要がApplication.DispatcherUnhandledException
ありますか?もしそうなら、どうすればこれを達成できますか? 最終的には、これらの例外を にスローしたいと思いBackgroundWorker
ます。提案やコメントをいただければ幸いです。
アップデート
私は await/async と Tasks で TPL を使用することに取り組んでおり、コードを更新しました。これは、例外を に戻しているので、ある程度成功していBackgroundWorker
ます。今のところ、例外をイベントに戻す方法は無視しますDoWork
... try/catch ブロックを追加して例外が発生していることを確認し、例外をキャッチして再スローしています。私のDoWork
イベントはこちら
private async void backgroundWorkerRefreshFromWeb_DoWork(object sender, DoWorkEventArgs e)
{
// Do not access the form's BackgroundWorker reference directly.
// Instead, use the reference provided by the sender parameter.
BackgroundWorker bw = sender as BackgroundWorker;
// Start the time-consuming operation.
NflStatsComplete = false;
bw.ReportProgress(0, "Starting Data Refresh from Web...");
try
{
e.Result = await Html.RetrieveWebData(bw, e);
}
catch (Exception ex)
{
throw;
}
// If the operation was canceled by the user,
// set the DoWorkEventArgs.Cancel property to true.
if (bw.CancellationPending)
{
e.Cancel = true;
}
}
デバッガーで例外が発生し、それがスローされるのを確認します。ただし、その後RunWorkerCompleted
イベントにRunWorkerCompletedEventArgs e
行くと、 e.Error == null
. DoWork
イベントから直接例外をスローしているため、これがどのようになるかわかりません。誰かがこの動作を説明できますか?