网上演练
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

375 lines
13 KiB

// CancellationTokenSource.cs
//
// Authors:
// Jérémie "Garuma" Laval <jeremie.laval@gmail.com>
// Marek Safar (marek.safar@gmail.com)
// Alfonso J. Ramos (theraot@gmail.com)
//
// Copyright (c) 2009 Jérémie "Garuma" Laval
// Copyright 2011 Xamarin, Inc (http://www.xamarin.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if NET20 || NET30 || NET35 || !NET_4_6
using System.Collections.Generic;
using LinqInternal.Collections.ThreadSafe;
namespace System.Threading
{
public class CancellationTokenSource : IDisposable
{
internal static readonly CancellationTokenSource CanceledSource = new CancellationTokenSource(); // Leaked
internal static readonly CancellationTokenSource NoneSource = new CancellationTokenSource(); // Leaked
private static readonly Action<CancellationTokenSource> _timerCallback;
private readonly ManualResetEvent _handle;
private SafeDictionary<CancellationTokenRegistration, Action> _callbacks;
private int _cancelRequested;
private int _currentId = int.MaxValue;
private int _disposeRequested;
private CancellationTokenRegistration[] _linkedTokens;
private LinqInternal.Threading.Timeout<CancellationTokenSource> _timeout;
static CancellationTokenSource()
{
CanceledSource._cancelRequested = 1;
_timerCallback = cancellationTokenSource =>
{
var callbacks = cancellationTokenSource._callbacks;
if (callbacks != null)
{
cancellationTokenSource.CancelExtracted(false, callbacks, true);
}
};
}
public CancellationTokenSource()
{
_callbacks = new SafeDictionary<CancellationTokenRegistration, Action>();
_handle = new ManualResetEvent(false);
}
public CancellationTokenSource(int millisecondsDelay)
: this()
{
if (millisecondsDelay < -1)
{
throw new ArgumentOutOfRangeException("millisecondsDelay");
}
if (millisecondsDelay != Timeout.Infinite)
{
_timeout = new LinqInternal.Threading.Timeout<CancellationTokenSource>(_timerCallback, millisecondsDelay, this);
}
}
public CancellationTokenSource(TimeSpan delay)
: this(CheckTimeout(delay))
{
//Empty
}
public bool IsCancellationRequested
{
get { return _cancelRequested == 1; }
}
public CancellationToken Token
{
get
{
CheckDisposed();
return new CancellationToken(this);
}
}
internal WaitHandle WaitHandle
{
get
{
CheckDisposed();
return _handle;
}
}
public static CancellationTokenSource CreateLinkedTokenSource(CancellationToken token1, CancellationToken token2)
{
return CreateLinkedTokenSource(new[] { token1, token2 });
}
public static CancellationTokenSource CreateLinkedTokenSource(params CancellationToken[] tokens)
{
if (tokens == null)
{
throw new ArgumentNullException("tokens");
}
if (tokens.Length == 0)
{
throw new ArgumentException("Empty tokens array");
}
var src = new CancellationTokenSource();
Action action = src.SafeLinkedCancel;
var registrations = new List<CancellationTokenRegistration>(tokens.Length);
foreach (var token in tokens)
{
if (token.CanBeCanceled)
{
registrations.Add(token.Register(action));
}
}
src._linkedTokens = registrations.ToArray();
return src;
}
public void Cancel()
{
Cancel(false);
}
public void Cancel(bool throwOnFirstException)
{
// If throwOnFirstException is true we throw exception as soon as they appear otherwise we aggregate them
var callbacks = CheckDisposedGetCallbacks();
CancelExtracted(throwOnFirstException, callbacks, false);
}
public void CancelAfter(TimeSpan delay)
{
CancelAfter(CheckTimeout(delay));
}
public void CancelAfter(int millisecondsDelay)
{
if (millisecondsDelay < -1)
{
throw new ArgumentOutOfRangeException("millisecondsDelay");
}
CheckDisposed();
if (Thread.VolatileRead(ref _cancelRequested) == 0 && millisecondsDelay != Timeout.Infinite)
{
if (_timeout == null)
{
// Have to be careful not to create secondary background timer
var newTimer = new LinqInternal.Threading.Timeout<CancellationTokenSource>(_timerCallback, Timeout.Infinite, this);
var oldTimer = Interlocked.CompareExchange(ref _timeout, newTimer, null);
if (!ReferenceEquals(oldTimer, null))
{
newTimer.Cancel();
}
_timeout.Change(millisecondsDelay);
}
}
}
public void Dispose()
{
Dispose(true);
}
internal void CheckDisposed()
{
if (Thread.VolatileRead(ref _disposeRequested) == 1)
{
throw new ObjectDisposedException(GetType().Name);
}
}
internal SafeDictionary<CancellationTokenRegistration, Action> CheckDisposedGetCallbacks()
{
var result = _callbacks;
if (result == null || Thread.VolatileRead(ref _disposeRequested) == 1)
{
throw new ObjectDisposedException(GetType().Name);
}
return result;
}
internal CancellationTokenRegistration Register(Action callback, bool useSynchronizationContext)
{
// NOTICE this method has no null check
var callbacks = CheckDisposedGetCallbacks();
var tokenReg = new CancellationTokenRegistration(Interlocked.Decrement(ref _currentId), this);
// If the source is already canceled run the callback inline.
// if not, we try to add it to the queue and if it is currently being processed.
// we try to execute it back ourselves to be sure the callback is ran.
if (Thread.VolatileRead(ref _cancelRequested) == 1)
{
callback();
}
else
{
// Capture execution contexts if the callback may not run inline.
if (useSynchronizationContext)
{
var capturedSyncContext = SynchronizationContext.Current;
var originalCallback = callback;
callback = () => capturedSyncContext.Send(_ => originalCallback(), null);
}
callbacks.TryAdd(tokenReg, callback);
// Check if the source was just canceled and if so, it may be that it executed the callbacks except the one just added...
// So try to inline the callback
if (Thread.VolatileRead(ref _cancelRequested) == 1 && callbacks.Remove(tokenReg, out callback))
{
callback();
}
}
return tokenReg;
}
internal bool RemoveCallback(CancellationTokenRegistration reg)
{
// Ignore call if the source has been disposed
if (Thread.VolatileRead(ref _disposeRequested) == 0)
{
var callbacks = _callbacks;
if (callbacks != null)
{
Action dummy;
return callbacks.Remove(reg, out dummy);
}
}
return true;
}
protected virtual void Dispose(bool disposing)
{
if (disposing && Interlocked.CompareExchange(ref _disposeRequested, 1, 0) == 0)
{
if (Thread.VolatileRead(ref _cancelRequested) == 0)
{
UnregisterLinkedTokens();
_callbacks = null;
}
var timer = Interlocked.Exchange(ref _timeout, null);
if (timer != null)
{
timer.Cancel();
}
_handle.Close();
}
}
private static int CheckTimeout(TimeSpan delay)
{
try
{
return checked((int)delay.TotalMilliseconds);
}
catch (OverflowException)
{
throw new ArgumentOutOfRangeException("delay");
}
}
private static void RunCallback(bool throwOnFirstException, Action callback, ref List<Exception> exceptions)
{
// NOTICE this method has no null check
if (throwOnFirstException)
{
callback();
}
else
{
try
{
callback();
}
catch (Exception exception)
{
if (ReferenceEquals(exceptions, null))
{
exceptions = new List<Exception>();
}
exceptions.Add(exception);
}
}
}
private void CancelExtracted(bool throwOnFirstException, SafeDictionary<CancellationTokenRegistration, Action> callbacks, bool ignoreDisposedException)
{
if (Interlocked.CompareExchange(ref _cancelRequested, 1, 0) == 0)
{
try
{
// The CancellationTokenSource may have been disposed jusst before this call
_handle.Set();
}
catch (ObjectDisposedException)
{
if (!ignoreDisposedException)
{
throw;
}
}
UnregisterLinkedTokens();
List<Exception> exceptions = null;
try
{
var id = _currentId;
do
{
Action callback;
var checkId = id;
if (callbacks.Remove(id, registration => registration.Equals(checkId, this), out callback) && callback != null)
{
RunCallback(throwOnFirstException, callback, ref exceptions);
}
} while (id++ != int.MaxValue);
}
finally
{
// Whatever was added after the cancellation process started, it should run inline in Register... if they don't, handle then here.
foreach (
var callback in
callbacks.RemoveWhereKeyEnumerable(_ => true))
{
RunCallback(throwOnFirstException, callback, ref exceptions);
}
}
if (exceptions != null)
{
throw new AggregateException(exceptions);
}
}
}
private void SafeLinkedCancel()
{
var callbacks = _callbacks;
if (callbacks == null || Thread.VolatileRead(ref _disposeRequested) == 1)
{
return;
}
CancelExtracted(false, callbacks, true);
}
private void UnregisterLinkedTokens()
{
var registrations = Interlocked.Exchange(ref _linkedTokens, null);
if (!ReferenceEquals(registrations, null))
{
foreach (var linked in registrations)
{
linked.Dispose();
}
}
}
}
}
#endif