private void StartAsynchronousComputation(AsynchronousComputationToStart computationToStart, Request requestToCompleteSynchronously, CancellationToken callerCancellationToken) { var cancellationToken = computationToStart.CancellationTokenSource.Token; try { cancellationToken.ThrowIfCancellationRequested(); // DO NOT ACCESS ANY FIELDS OR STATE BEYOND THIS POINT. Since this function // runs unsynchronized, it's possible that during this function this request // might be cancelled, and then a whole additional request might start and // complete inline, and cache the result. By grabbing state before we check // the cancellation token, we can be assured that we are only operating on // a state that was complete. try { // We avoid creating a full closure just to pass the token along // Also, use TaskContinuationOptions.ExecuteSynchronously so that we inline // the continuation if asynchronousComputeFunction completes synchronously var task = computationToStart.AsynchronousComputeFunction(cancellationToken); task.ContinueWith( (t, s) => CompleteWithTask(t, ((CancellationTokenSource)s).Token), computationToStart.CancellationTokenSource, cancellationToken, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); if (requestToCompleteSynchronously != null && task.IsCompleted) { using (TakeLock(CancellationToken.None)) { task = GetCachedValueAndCacheThisValueIfNoneCached_NoLock(task); } // It's safe to synchronously complete this task, since the Task object hasn't been returned // to the caller of GetValueAsync yet requestToCompleteSynchronously.CompleteFromTaskSynchronously(task); } } catch (Exception e) when(FatalError.ReportUnlessCanceled(e)) { throw ExceptionUtilities.Unreachable; } } catch (OperationCanceledException oce) when(CrashIfCanceledWithDifferentToken(oce, cancellationToken)) { // The underlying computation cancelled with the correct token, but we must ourselves ensure that the caller // on our stack gets an OperationCanceledException thrown with the right token callerCancellationToken.ThrowIfCancellationRequested(); // We can only be here if the computation was cancelled, which means all requests for the value // must have been cancelled. Therefore, the ThrowIfCancellationRequested above must have thrown // because that token from the requestor was cancelled. throw ExceptionUtilities.Unreachable; } }
private void StartAsynchronousComputation(AsynchronousComputationToStart computationToStart, Request requestToCompleteSynchronously) { var cancellationToken = computationToStart.CancellationTokenSource.Token; try { cancellationToken.ThrowIfCancellationRequested(); // DO NOT ACCESS ANY FIELDS OR STATE BEYOND THIS POINT. Since this function // runs unsynchronized, it's possible that during this function this request // might be cancelled, and then a whole additional request might start and // complete inline, and cache the result. By grabbing state before we check // the cancellation token, we can be assured that we are only operating on // a state that was complete. try { // We avoid creating a full closure just to pass the token along // Also, use TaskContinuationOptions.ExecuteSynchronously so that we inline // the continuation if asynchronousComputeFunction completes synchronously var task = computationToStart.AsynchronousComputeFunction(cancellationToken); task.ContinueWith( (t, s) => CompleteWithTask(t, ((CancellationTokenSource)s).Token), computationToStart.CancellationTokenSource, cancellationToken, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); if (requestToCompleteSynchronously != null && task.IsCompleted) { using (TakeLock(CancellationToken.None)) { task = GetCachedValueAndCacheThisValueIfNoneCached_NoLock(task); } requestToCompleteSynchronously.CompleteFromTaskSynchronously(task); } } catch (Exception e) when(FatalError.ReportUnlessCanceled(e)) { throw ExceptionUtilities.Unreachable; } } catch (OperationCanceledException oce) when(CrashIfCanceledWithDifferentToken(oce, cancellationToken)) { // As long as it's the right token, this means that our thread was the first thread // to start an asynchronous computation, but the requestor cancelled as we were starting up // the computation. throw ExceptionUtilities.Unreachable; } }
private void StartAsynchronousComputation(AsynchronousComputationToStart computationToStart, Request requestToCompleteSynchronously, CancellationToken callerCancellationToken) { var cancellationToken = computationToStart.CancellationTokenSource.Token; try { cancellationToken.ThrowIfCancellationRequested(); // DO NOT ACCESS ANY FIELDS OR STATE BEYOND THIS POINT. Since this function // runs unsynchronized, it's possible that during this function this request // might be cancelled, and then a whole additional request might start and // complete inline, and cache the result. By grabbing state before we check // the cancellation token, we can be assured that we are only operating on // a state that was complete. try { var task = computationToStart.AsynchronousComputeFunction(cancellationToken); // As an optimization, if the task is already completed, mark the // request as being completed as well. // // Note: we want to do this before we do the .ContinueWith below. That way, // when the async call to CompleteWithTask runs, it sees that we've already // completed and can bail immediately. If we were to do this after we // kicked off the async work, then we'd have the chance that both would // run concurrently and we'd have a higher change of hitting the race condition // of calling AsyncMethodBuilder.SetResult simultaneously (and thus having // the InvalidOperationException that we have to ignore). if (requestToCompleteSynchronously != null && task.IsCompleted) { using (TakeLock(CancellationToken.None)) { task = GetCachedValueAndCacheThisValueIfNoneCached_NoLock(task); } // It's safe to synchronously complete this task, since the Task object hasn't been returned // to the caller of GetValueAsync yet requestToCompleteSynchronously.CompleteFromTaskSynchronously(task); } // We avoid creating a full closure just to pass the token along // Also, use TaskContinuationOptions.ExecuteSynchronously so that we inline // the continuation if asynchronousComputeFunction completes synchronously task.ContinueWith( (t, s) => CompleteWithTask(t, ((CancellationTokenSource)s).Token), computationToStart.CancellationTokenSource, cancellationToken, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } catch (Exception e) when(FatalError.ReportUnlessCanceled(e)) { throw ExceptionUtilities.Unreachable; } } catch (OperationCanceledException oce) when(CrashIfCanceledWithDifferentToken(oce, cancellationToken)) { // The underlying computation cancelled with the correct token, but we must ourselves ensure that the caller // on our stack gets an OperationCanceledException thrown with the right token callerCancellationToken.ThrowIfCancellationRequested(); // We can only be here if the computation was cancelled, which means all requests for the value // must have been cancelled. Therefore, the ThrowIfCancellationRequested above must have thrown // because that token from the requester was cancelled. throw ExceptionUtilities.Unreachable; } }