バックグラウンドワーカーのDoWorkをキャンセルするC#
-
05-07-2019 - |
質問
C#2008
以下のコードを使用してソフトフォンにログインしています。ただし、ログインの進行は、初期化する必要のあるものや確認する必要があるものが多いため、長いプロセスです。コードを投稿するのに時間がかかるので、ここではほんの少しだけ追加しました。
以下のコードでは、各チェックを行う前に、キャンセルボタンのクリックイベントでCancelAsyncが呼び出された場合にCancellationPendingをチェックしています。これは正しいです?また、チェックに失敗した場合は、CancelAsyncを呼び出してe.Cancelをtrueに設定します。
ここで使用した方法が、使用するのに最適な方法であるかどうかを知りたい。
アドバイスをありがとう、
private void bgwProcessLogin_DoWork(object sender, DoWorkEventArgs e)
{
/*
* Perform at test to see if the background worker has been
* cancelled by the user before attemping to continue to login.
*
* Cancel background worker on any failed attemp to login
*/
// Start with cancel being false as to reset this if cancel has been set to true
// in the cancel button.
e.Cancel = false;
NetworkingTest connection_test = new NetworkingTest();
if (!this.bgwProcessLogin.CancellationPending)
{
// Check local LAN or Wireless connection
if (!connection_test.IsNetworkConnected())
{
// Update label
if (this.lblRegistering.InvokeRequired)
{
this.lblRegistering.Invoke(new UpdateRegisterLabelDelegate(UpdateRegisterLabel), "No network connection");
}
else
{
this.lblRegistering.Text = "No network connection";
}
// Failed attemp
this.bgwProcessLogin.CancelAsync();
e.Cancel = true;
return;
}
// Report current progress
this.bgwProcessLogin.ReportProgress(0, "Network connected");
}
else
{
// User cancelled
e.Cancel = true;
return;
}
// Test if access to Server is available
if (!this.bgwProcessLogin.CancellationPending)
{
if (!connection_test.IsSIPServerAvailable())
{
// Update label
if (this.lblRegistering.InvokeRequired)
{
this.lblRegistering.Invoke(new UpdateRegisterLabelDelegate(UpdateRegisterLabel), "Server unavailable");
}
else
{
this.lblRegistering.Text = "Server unavailable";
}
// Failed attemp
this.bgwProcessLogin.CancelAsync();
e.Cancel = true;
return;
}
// Report current progress
this.bgwProcessLogin.ReportProgress(1, "Server available");
}
else
{
// User cancelled
e.Cancel = true;
return;
}
.
.
.
}
private void bgwProcessLogin_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Check for any errors
if (e.Error == null)
{
if (e.Cancelled)
{
// User cancelled login or login failed
}
else
{
// Login completed successfully
}
}
else
{
// Something failed display error
this.statusDisplay1.CallStatus = e.Error.Message;
}
}
private void bgwProcessLogin_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.lblRegistering.Text = e.UserState.ToString();
}
private void btnCancel_Click(object sender, EventArgs e)
{
// Cancel the logging in process
this.bgwProcessLogin.CancelAsync();
this.lblRegistering.Text = "Logged out";
}
解決
問題は1つだけかもしれません。DoWorkイベントハンドラーの操作の1つが長時間続く場合です。この場合、保留中の操作は、その操作が終了した後にのみ中止できます。 DoWorkイベントのすべての操作が非常に長く続くことができない場合(たとえば、5秒以内)、すべてOKですが、この場合、操作の1つが長時間(たとえば5分)続く場合、ユーザーはこの操作が完了するまで待機します。
DoWorkに長期にわたる操作が含まれている場合、AbortableBackgroundWorkerなどを使用できます。このようなもの:
public class AbortableBackgroundWorker : BackgroundWorker
{
private Thread workerThread;
protected override void OnDoWork(DoWorkEventArgs e)
{
workerThread = Thread.CurrentThread;
try
{
base.OnDoWork(e);
}
catch (ThreadAbortException)
{
e.Cancel = true; //We must set Cancel property to true!
Thread.ResetAbort(); //Prevents ThreadAbortException propagation
}
}
public void Abort()
{
if (workerThread != null)
{
workerThread.Abort();
workerThread = null;
}
}
}
この場合、保留中の操作を本当に中止できますが、いくつかの制限もあります(マネージスレッドの中止といくつかの制限の詳細については、ローターを使用したThreadAbortExceptionの深さの配管)。
PS私は、より使いやすい形式でInvokeRequiredをラップする必要があることにOliverに同意します。
他のヒント
あなたはそれを正しい方法でやっていると思う。スレッドを終了または中止できるスレッドメンバがありますが、このような目的には使用しないでください。すべての「キャンセル」を行うのは少し奇妙に見えるかもしれません。コードをチェックインしますが、スレッドを終了するタイミングを正確に制御できます。 「失礼」にした場合ワーカースレッドを中止します。スレッドは終了するタイミングを制御できず、状態が破損している可能性があります。
DoWork()関数内で ...
を記述しました。表示されている2つのタスクのように、同じ構造のタスクの数に応じて、この構造を独自のメソッドにリファクタリングし、変更部分をパラメーターとして与えることができます。
また、このInvokeRequired if-elseブランチは出力文字列を2倍にしました。ここでStackoverflowまたはWebを少し検索すると、この倍増を達成するためのパターンが表示されるはずです。
他のすべては非常に良く見えます。
this.bgwProcessLogin.CancelAsync();を呼び出す必要がないことが1つあります。 e.Cancel = true;を設定するだけなので、