以下をキャンセルする正しい方法は何ですか?
_var tcpListener = new TcpListener(connection);
tcpListener.Start();
var client = await tcpListener.AcceptTcpClientAsync();
_
単にtcpListener.Stop()
を呼び出すとObjectDisposedException
が生成され、AcceptTcpClientAsync
メソッドはCancellationToken
構造体を受け入れません。
私は明らかな何かを完全に欠いていますか?
Stop
クラス で TcpListener
メソッド を呼び出さない場合、ここでは完全な解決策はありません。
操作が特定の時間枠内に完了しないときに通知を受けても、元の操作を完了できる場合は、次のような拡張メソッドを作成できます。
public static async Task<T> WithWaitCancellation<T>(
this Task<T> task, CancellationToken cancellationToken)
{
// The tasck completion source.
var tcs = new TaskCompletionSource<bool>();
// Register with the cancellation token.
using(cancellationToken.Register( s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs) )
{
// If the task waited on is the cancellation token...
if (task != await Task.WhenAny(task, tcs.Task))
throw new OperationCanceledException(cancellationToken);
}
// Wait for one or the other to complete.
return await task;
}
上記は Stephen Toubのブログ投稿「キャンセルできない非同期操作をキャンセルするにはどうすればよいですか?」 からのものです。
AcceptTcpClientAsync
method のオーバーロードがないため、これは実際にはcancel操作ではありません。 CancellationToken
、キャンセルできませんable。
つまり、拡張メソッドがキャンセルdidが発生したことを示している場合、元の Task
、のコールバックの待機をキャンセルしています。 not操作自体をキャンセルします。
そのために、メソッドの名前をWithCancellation
からWithWaitCancellation
に変更して、実際のアクションではなくwaitをキャンセルしていることを示しています。
そこから、コードで簡単に使用できます。
// Create the listener.
var tcpListener = new TcpListener(connection);
// Start.
tcpListener.Start();
// The CancellationToken.
var cancellationToken = ...;
// Have to wait on an OperationCanceledException
// to see if it was cancelled.
try
{
// Wait for the client, with the ability to cancel
// the *wait*.
var client = await tcpListener.AcceptTcpClientAsync().
WithWaitCancellation(cancellationToken);
}
catch (AggregateException ae)
{
// Async exceptions are wrapped in
// an AggregateException, so you have to
// look here as well.
}
catch (OperationCancelledException oce)
{
// The operation was cancelled, branch
// code here.
}
待機がキャンセルされた場合にスローされる OperationCanceledException
インスタンスをキャプチャするには、クライアントの呼び出しをラップする必要があることに注意してください。
非同期操作からスローされると例外がラップされるため、 AggregateException
キャッチでもスローしました(この場合は、自分でテストする必要があります)。
Stop
method (基本的に、何が起こっているかにかかわらず、すべてを激しく破壊するものはすべて、 )、もちろん、これはあなたの状況に依存します。
待機しているリソース(この場合はTcpListener
)を共有していない場合は、リソースをより適切に使用して、abortメソッドを呼び出し、待機している操作から発生する例外をすべて飲み込んでください。 (stopを呼び出すときにビットを反転し、操作を待機している他の領域でそのビットを監視する必要があります)。これによりコードが多少複雑になりますが、リソースの使用とクリーンアップをできるだけ早く心配する必要があり、この選択肢を利用できる場合は、これが適しています。
リソース使用率がnot問題であり、より協調的なメカニズムに慣れていて、リソースをnot共有している場合は、WithWaitCancellation
メソッドを使用するのが適切です。ここでの長所は、コードがよりクリーンで、保守が容易であることです。
CasperOneの答えは正しいですが、同じ目的を達成するWithCancellation
(またはWithWaitCancellation
)拡張メソッドには、よりクリーンな潜在的な実装があります。
static Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)
{
return task.IsCompleted
? task
: task.ContinueWith(
completedTask => completedTask.GetAwaiter().GetResult(),
cancellationToken,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
}
CancellationToken
パラメーターを渡します。TaskContinuationOptions.ExecuteSynchronously
)、そうでない場合はThreadPool
スレッドを使用します(TaskScheduler.Default
)キャンセルのためにCancellationToken
を監視しているとき。CancellationToken
がキャンセルされる前に元のタスクが完了すると、返されたタスクが結果を保存します。それ以外の場合、タスクはキャンセルされ、待機するとTaskCancelledException
がスローされます。