194 lines
8.1 KiB
C#
194 lines
8.1 KiB
C#
|
//-----------------------------------------------------------------------------
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
|
||
|
namespace System.Runtime
|
||
|
{
|
||
|
using System;
|
||
|
using System.Diagnostics;
|
||
|
using System.Diagnostics.CodeAnalysis;
|
||
|
using System.Threading;
|
||
|
using System.Transactions;
|
||
|
|
||
|
// AsyncResult starts acquired; Complete releases.
|
||
|
[Fx.Tag.SynchronizationPrimitive(Fx.Tag.BlocksUsing.ManualResetEvent, SupportsAsync = true, ReleaseMethod = "Complete")]
|
||
|
abstract class TransactedAsyncResult : AsyncResult
|
||
|
{
|
||
|
IAsyncResult deferredTransactionalResult;
|
||
|
TransactionSignalScope transactionContext;
|
||
|
|
||
|
protected TransactedAsyncResult(AsyncCallback callback, object state)
|
||
|
: base(callback, state)
|
||
|
{
|
||
|
SetBeforePrepareAsyncCompletionAction(BeforePrepareAsyncCompletion);
|
||
|
SetCheckSyncValidationFunc(CheckSyncValidation);
|
||
|
}
|
||
|
|
||
|
protected override bool OnContinueAsyncCompletion(IAsyncResult result)
|
||
|
{
|
||
|
if (this.transactionContext != null && !this.transactionContext.Signal(result))
|
||
|
{
|
||
|
// The TransactionScope isn't cleaned up yet and can't be done on this thread. Must defer
|
||
|
// the callback (which is likely to attempt to commit the transaction) until later.
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
this.transactionContext = null;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool CheckSyncValidation(IAsyncResult result)
|
||
|
{
|
||
|
if (result.CompletedSynchronously)
|
||
|
{
|
||
|
// Once we pass the check, we know that we own forward progress, so transactionContext is correct. Verify its state.
|
||
|
if (this.transactionContext != null)
|
||
|
{
|
||
|
if (this.transactionContext.State != TransactionSignalState.Completed)
|
||
|
{
|
||
|
ThrowInvalidAsyncResult("Check/SyncContinue cannot be called from within the PrepareTransactionalCall using block.");
|
||
|
}
|
||
|
else if (this.transactionContext.IsSignalled)
|
||
|
{
|
||
|
// This is most likely to happen when result.CompletedSynchronously registers differently here and in the callback, which
|
||
|
// is the fault of 'result'.
|
||
|
ThrowInvalidAsyncResult(result);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if (object.ReferenceEquals(result, this.deferredTransactionalResult))
|
||
|
{
|
||
|
// The transactionContext may not be current if forward progress has been made via the callback. Instead,
|
||
|
// use deferredTransactionalResult to see if we are supposed to execute a post-transaction callback.
|
||
|
//
|
||
|
// Once we pass the check, we know that we own forward progress, so transactionContext is correct. Verify its state.
|
||
|
if (this.transactionContext == null || !this.transactionContext.IsSignalled)
|
||
|
{
|
||
|
ThrowInvalidAsyncResult(result);
|
||
|
}
|
||
|
this.deferredTransactionalResult = null;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
this.transactionContext = null;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void BeforePrepareAsyncCompletion()
|
||
|
{
|
||
|
if (this.transactionContext != null)
|
||
|
{
|
||
|
// It might be an old, leftover one, if an exception was thrown within the last using (PrepareTransactionalCall()) block.
|
||
|
if (this.transactionContext.IsPotentiallyAbandoned)
|
||
|
{
|
||
|
this.transactionContext = null;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
this.transactionContext.Prepared();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected IDisposable PrepareTransactionalCall(Transaction transaction)
|
||
|
{
|
||
|
if (this.transactionContext != null && !this.transactionContext.IsPotentiallyAbandoned)
|
||
|
{
|
||
|
ThrowInvalidAsyncResult("PrepareTransactionalCall should only be called as the object of non-nested using statements. If the Begin succeeds, Check/SyncContinue must be called before another PrepareTransactionalCall.");
|
||
|
}
|
||
|
|
||
|
return this.transactionContext = transaction == null ? null : new TransactionSignalScope(this, transaction);
|
||
|
}
|
||
|
|
||
|
enum TransactionSignalState
|
||
|
{
|
||
|
Ready = 0,
|
||
|
Prepared,
|
||
|
Completed,
|
||
|
Abandoned,
|
||
|
}
|
||
|
|
||
|
class TransactionSignalScope : SignalGate<IAsyncResult>, IDisposable
|
||
|
{
|
||
|
TransactionScope transactionScope;
|
||
|
TransactedAsyncResult parent;
|
||
|
|
||
|
public TransactionSignalScope(TransactedAsyncResult result, Transaction transaction)
|
||
|
{
|
||
|
Fx.Assert(transaction != null, "Null Transaction provided to AsyncResult.TransactionSignalScope.");
|
||
|
this.parent = result;
|
||
|
this.transactionScope = TransactionHelper.CreateTransactionScope(transaction);
|
||
|
}
|
||
|
|
||
|
public TransactionSignalState State { get; private set; }
|
||
|
|
||
|
public bool IsPotentiallyAbandoned
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return State == TransactionSignalState.Abandoned || (State == TransactionSignalState.Completed && !IsSignalled);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void Prepared()
|
||
|
{
|
||
|
if (State != TransactionSignalState.Ready)
|
||
|
{
|
||
|
AsyncResult.ThrowInvalidAsyncResult("PrepareAsyncCompletion should only be called once per PrepareTransactionalCall.");
|
||
|
}
|
||
|
State = TransactionSignalState.Prepared;
|
||
|
}
|
||
|
|
||
|
void IDisposable.Dispose()
|
||
|
{
|
||
|
if (State == TransactionSignalState.Ready)
|
||
|
{
|
||
|
State = TransactionSignalState.Abandoned;
|
||
|
}
|
||
|
else if (State == TransactionSignalState.Prepared)
|
||
|
{
|
||
|
State = TransactionSignalState.Completed;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
AsyncResult.ThrowInvalidAsyncResult("PrepareTransactionalCall should only be called in a using. Dispose called multiple times.");
|
||
|
}
|
||
|
|
||
|
try
|
||
|
{
|
||
|
TransactionHelper.CompleteTransactionScope(ref this.transactionScope);
|
||
|
}
|
||
|
catch (Exception exception)
|
||
|
{
|
||
|
if (Fx.IsFatal(exception))
|
||
|
{
|
||
|
throw;
|
||
|
}
|
||
|
|
||
|
// Complete and Dispose are not expected to throw. If they do it can mess up the AsyncResult state machine.
|
||
|
throw Fx.Exception.AsError(new InvalidOperationException(SRCore.AsyncTransactionException));
|
||
|
}
|
||
|
|
||
|
// This will release the callback to run, or tell us that we need to defer the callback to Check/SyncContinue.
|
||
|
//
|
||
|
// It's possible to avoid this Interlocked when CompletedSynchronously is true, but we have no way of knowing that
|
||
|
// from here, and adding a way would add complexity to the AsyncResult transactional calling pattern. This
|
||
|
// unnecessary Interlocked only happens when: PrepareTransactionalCall is called with a non-null transaction,
|
||
|
// PrepareAsyncCompletion is reached, and the operation completes synchronously or with an exception.
|
||
|
IAsyncResult result;
|
||
|
if (State == TransactionSignalState.Completed && Unlock(out result))
|
||
|
{
|
||
|
if (this.parent.deferredTransactionalResult != null)
|
||
|
{
|
||
|
AsyncResult.ThrowInvalidAsyncResult(this.parent.deferredTransactionalResult);
|
||
|
}
|
||
|
this.parent.deferredTransactionalResult = result;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|