一定の時間スリープするのに最適な方法は何ですか?ただし、IsCancellationRequested
からのCancellationToken
によって中断されることができますか?
.NET 4.0で機能するソリューションを探しています。
書きたい
void MyFunc (CancellationToken ct)
{
//...
// simulate some long lasting operation that should be cancelable
Thread.Sleep(TimeSpan.FromMilliseconds(10000), ct);
}
私はそれについてここにブログを書いた:
CancellationTokenおよびThread.Sleep
要するに:
var cancelled = token.WaitHandle.WaitOne(TimeSpan.FromSeconds(5));
あなたの文脈で:
void MyFunc (CancellationToken ct)
{
//...
// simulate some long lasting operation that should be cancelable
var cancelled = ct.WaitHandle.WaitOne(TimeSpan.FromSeconds(10));
}
あるいは、これはかなり明確だと思います:
Task.Delay(waitTimeInMs, cancellationToken).Wait(cancellationToken);
一定時間後に非同期操作をキャンセルする一方で、手動で操作をキャンセルできるようにするには、次のようなものを使用します
_CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
cts.CancelAfter(5000);
_
これにより、5秒後にキャンセルされます。自己の操作をキャンセルするには、token
をasyncメソッドに渡し、token.ThrowifCancellationRequested()
メソッドを使用します。このメソッドでは、cts.Cancel()
。
したがって、完全な例は次のとおりです。
_CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
cts.CancelAfter(5000);
// Set up the event handler on some button.
if (cancelSource != null)
{
cancelHandler = delegate
{
Cancel(cts);
};
stopButton.Click -= cancelHandler;
stopButton.Click += cancelHandler;
}
// Now launch the method.
SomeMethodAsync(token);
_
stopButton
は、実行中のタスクをキャンセルするためにクリックするボタンです
_private void Cancel(CancellationTokenSource cts)
{
cts.Cancel();
}
_
メソッドは次のように定義されます
_SomeMethodAsync(CancellationToken token)
{
Task t = Task.Factory.StartNew(() =>
{
msTimeout = 5000;
Pump(token);
}, token,
TaskCreationOptions.None,
TaskScheduler.Default);
}
_
ここで、スレッドを機能させると同時にユーザーのキャンセルを有効にするには、「ポンピング」メソッドを記述する必要があります
_int msTimeout;
bool timeLimitReached = false;
private void Pump(CancellationToken token)
{
DateTime now = DateTime.Now;
System.Timer t = new System.Timer(100);
t.Elapsed -= t_Elapsed;
t.Elapsed += t_Elapsed;
t.Start();
while(!timeLimitReached)
{
Thread.Sleep(250);
token.ThrowIfCancellationRequested();
}
}
void t_Elapsed(object sender, ElapsedEventArgs e)
{
TimeSpan elapsed = DateTime.Now - this.readyUpInitialised;
if (elapsed > msTimeout)
{
timeLimitReached = true;
t.Stop();
t.Dispose();
}
}
_
SomeAsyncMethod
は呼び出し元に戻ることに注意してください。呼び出し側もブロックするには、Task
を呼び出し階層内で上に移動する必要があります。
私がこれまで見つけた最良の解決策は次のとおりです。
void MyFunc(CancellationToken ct)
{
//...
var timedOut = WaitHandle.WaitAny(new[] { ct.WaitHandle }, TimeSpan.FromMilliseconds(2000)) == WaitHandle.WaitTimeout;
var cancelled = ! timedOut;
}
更新:
これまでの最善の解決策は、 受け入れられた答え です。
CancellationToken.WaitHandle は、CancellationTokenSourceが破棄された後にアクセスされたときに例外をスローできます。
ObjectDisposedException:CancellationTokenSourceは破棄されました。
場合によっては、特に リンクされたキャンセルソース が手動で(必要に応じて)破棄される場合、これは迷惑になる可能性があります。
この拡張メソッドにより、「安全なキャンセル待ち」が可能になります。ただし、キャンセルトークンの状態および/または戻り値の使用方法のチェックおよび適切なフラグ付けと組み合わせて使用する必要があります。これは、WaitHandleにアクセスする例外を抑制し、予想よりも速く戻る可能性があるためです。
internal static class CancellationTokenExtensions
{
/// <summary>
/// Wait up to a given duration for a token to be cancelled.
/// Returns true if the token was cancelled within the duration
/// or the underlying cancellation token source has been disposed.
/// </summary>
public static bool WaitForCancellation(this CancellationToken token, TimeSpan duration)
{
WaitHandle handle;
try
{
handle = token.WaitHandle;
}
catch
{
// eg. CancellationTokenSource is disposed
return true;
}
return handle.WaitOne(duration);
}
}