私は TPL を少しいじっていますが、これは関連するタスクです。
質問 1 :タスクをクラスに組み込む方法に関して、正しい道を進んでいるかどうかについてのフィードバックが必要です。
このクラスには、Start メソッドと Stop メソッドがあります。
Start の実装では、処理を実行するためのファイア アンド フォーゲット タスクを作成したいと考えています。インスタンスの Start メソッドを呼び出すコードは、必要に応じて自由に Stop を呼び出す必要があります (たとえば、呼び出しコードは Windows サービスであるため、OnStop イベントでは任意のインスタンスで Stop を呼び出したい場合があります。つまり、「今すぐシャットダウンしたいので、全員があなたがしていることをやめなさい!」)。
次のようなコードをたくさん見ました
Task myTask = Task.Factory.StartNew(GoOffAndDoSomething, [associated cancellation token]);
try{
myTask.Wait();
}catch (AggregateException ae){
//Process aggregate exceptions as required
}
...しかし、これが呼び出し元のスレッドをブロックしていて、Stop メソッドを呼び出す (または他のことを行う) ことができないため、待機したくありません。
だから、こういうことをしなければいけないと思って……。
Task myTask = Task.Factory.StartNew(GoOffAndDoSomething, [associated cancellation token]);
//Use non-blocking ContinueWith
myTask.ContinueWith(HandleBadStuffThatHappened, TaskContinuationOptions.NotOnRanToCompletion);
//The method to handle exceptions etc
Action<Task> HandleBadStuffThatHappened = (antecendent) =>
{
// "Observe" your antecedent's exception so as to avoid an exception
// being thrown on the finalizer thread
var badStuffHappened = antecendent.Exception;
//Handle\rethrow exception etc
};
質問 2 :これは私が取るべきアプローチの種類ですか?
このクラスの外で Start メソッドをタスクとして作成し、呼び出し元のコードでキャンセル\例外を処理することを提案する人もいるかもしれませんが、Windows サービスで作成されたクラスのインスタンスが多数ある可能性があるため、タスクの作成と処理が必要なだけです。クラス自体で行われる例外。
編集:ここで私自身の質問に答えることに近いですが、この追加情報が私の意図をより明確にし、他の人が追加する機会を提供する場合に備えて、しばらくの間コメントにオープンのままにします
だから...私は正しい道を進んでいたと思います。私のコメントの 1 つで述べたように、私が行ったさらなる調査に基づいて、アプローチをいじるために小さなアプリを作成しました。私のクラスの関連するメソッドをここに示します。これとそのコメントは、私の現在の提案されたアプローチを示しているはずです。私は基本的にそこにいると思います。
//Called by external client to get things rolling
public void Start()
{
//Could use Status property of Task but it is simpler to just use a class property than deal
//with all the different stages a Task can be in.
if (!IsRunning)
{
IsRunning = true; //set it first in case there are any delays\issues with starting up
_tokenSource = new CancellationTokenSource();
_processing = Task.Factory.StartNew(process, _tokenSource.Token);
//Use the OnlyOnFaulted task continuation option. This is different to
//my .NotOnRanToCompletion suggestion previously
_processing.ContinueWith(antecedent => HandleException(antecedent.Exception),
TaskContinuationOptions.OnlyOnFaulted);
//There's no 'Task.Wait' here, I just want to fire and forget this Task.
}
}
//Look at the call to ContinueWith in Start method. This is what I want to do if the Task goes to
//a Faulted state, ie an exception occurred.
private void HandleException(AggregateException ae)
{
IsRunning = false;
//Log or handle errors errors as required.
//ae.Flatten().InnerException will give the exception that caused the failure.
//Finally Dispose Task here. Typically I retry code a specified number of times
//(retry code not shown) before finally throwing the exception, and typically I don't do any
//explicit handling other than to Log\Alert the issue. So at this poin the Task is 'beyond saving'
//so get rid of it.
_processing.Dispose();
}
//Stop method which can be called by external client.
public void Stop()
{
//Use the cancellation token created in Start() to cancel the Task
_tokenSource.Cancel();
IsRunning = false; //set flag last in case something occurs during cancellation process
}
//What I wired up my Task to do
private void process()
{
while (!_tokenSource.IsCancellationRequested)
{
//So assuming normal UN-exceptional operation of your code then just
//do stuff here until Stop method called by client
}
}
クライアントへの例外のスローに関する注意
ある時点で、クライアントに例外をスローすることを調査し(これは元の質問と一致していないことはわかっていますが、興味がありました)、そこでハンドル\ログを記録しました。これを行う 1 つの方法 (私が見た他にもいくつかありました) は、例外が発生したときに、このクラスが例外をパラメーターとしてイベントを発生させることです。クライアントは、例外の通知を受けるために、このイベントにサブスクライブする必要があります。
私はこの機能を必要とせず、それは複雑なことです。代わりに、クラス自体で例外のすべての処理\ロギングを行っているだけです。
優れた TPL オプション ドキュメント
http://download.microsoft.com/download/B/C/F/BCFD4868-1354-45E3-B71B-B851CD78733D/TPLOptionsTour.pdf をご覧ください ここで、例外処理の一般的な用途である「OnlyOnFaulted」アプローチを使用するというアイデアを得ました (19 ページを参照)。
さらにコメントを歓迎します。