Calling a WCF operation marked with "TransactionScopeRequired = true" on a service that uses an asynchronous host implementation (in my case using Tasks) and throws an exception asynchronously (i.e. from a continuation), always seems to result in the following exception (instead of the exception I threw) being received on the client:
System.AggregateException: One or more errors occurred. ---> System.ServiceModel.ProtocolException: The transaction under which this method call was executing was asynchronously aborted.
at System.ServiceModel.Channels.ServiceChannel.ThrowIfFaultUnderstood(Message reply, MessageFault fault, String action, MessageVersion version, FaultConverter faultConverter)
at System.ServiceModel.Channels.ServiceChannel.HandleReply(ProxyOperationRuntime operation, ProxyRpc& rpc)
at System.ServiceModel.Channels.ServiceChannel.EndCall(String action, Object[] outs, IAsyncResult result)
at System.ServiceModel.Channels.ServiceChannelProxy.TaskCreator.<>c__DisplayClass6_0.<CreateTask>b__0(IAsyncResult asyncResult)
at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)
--- End of inner exception stack trace ---
at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
at System.Threading.Tasks.Task.Wait()
at WcfTransactionScopeRequiredAsync.Program.Main(String[] args) in D:\\WcfTransactionScopeRequiredAsync\\WcfTransactionScopeRequiredAsync.Client\\Program.cs:line 19
---> (Inner Exception #0) System.ServiceModel.ProtocolException: The transaction under which this method call was executing was asynchronously aborted.
at System.ServiceModel.Channels.ServiceChannel.ThrowIfFaultUnderstood(Message reply, MessageFault fault, String action, MessageVersion version, FaultConverter faultConverter)
at System.ServiceModel.Channels.ServiceChannel.HandleReply(ProxyOperationRuntime operation, ProxyRpc& rpc)
at System.ServiceModel.Channels.ServiceChannel.EndCall(String action, Object[] outs, IAsyncResult result)
at System.ServiceModel.Channels.ServiceChannelProxy.TaskCreator.<>c__DisplayClass6_0.<CreateTask>b__0(IAsyncResult asyncResult)
at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)<---
Service contract:
[ServiceContract]
public interface IService
{
[FaultContract(typeof(SomeFault))]
[TransactionFlow(TransactionFlowOption.Allowed)]
[OperationContract]
Task DoItAsync();
}
Service host implementation:
[OperationBehavior(TransactionScopeRequired = true)]
public async Task DoItAsync()
{
using (TransactionScope tx = new TransactionScope(TransactionScopeOption.Required, TransactionScopeAsyncFlowOption.Enabled))
{
await Task.Delay(1000).ConfigureAwait(continueOnCapturedContext: false);
throw new FaultException<SomeFault>(new SomeFault());
tx.Complete();
}
}
The client is synchronous:
service.DoItAsync().Wait();
The client does not flow a transaction in this case (so this is definitely not a distributed transaction (verified by checking Transaction.Current.TransactionInformation.DistributedIdentifier).
This code was tested with .NET framework 4.7.2.
My research so far suggests that this ProtocolException is thrown by the WCF pipeline on the service host (also evident from the stack trace in the Exception) and would supposedly be meant to deal with distributed transactions being aborted outside of the host's control (i.e when the DTC issues the abort). Although this appears to make some sense, this is clearly not the case here.
Is there any way to avoid this ProtocolException while still using an async implementation on the host?
Is there any way to avoid this ProtocolException while still using an async implementation on the host?
It appears you should not create your own TransactionScope instance in the service host implementation and instead rely solely on the TransactionScope created by the WCF pipeline.
I adapted the service host implementation code above accordingly and the ProtocolException dissapeared (I do now get the FaultException on the client).
Comments on this post put me on the right track.