Volatile IEnlistmentNotification and TransactionSc

2019-07-06 08:44发布

问题:

Apart from .NET 4.5.1 there is a new option on the TransactionScope which enables to use async flow. This allows to write the following client code

using(var txt = new TransactionScope(..., TransactionScopeAsyncFlowOption.Enabled)
{
   await sender.SendAsync();
}

So far so good. But when I need to implement a volatile IEnlistmentNotification I'm struggling to do that. Let's imagine the following scenario, assumption: My underlying infrastructure is completely async from bottom to top

public class MessageSender : ISendMessages
{
    public async Task SendAsync(TransportMessage message, SendOptions options)
    {
        await sender.SendAsync(message);
    }
}

So what I want to achieve is to introduce a volatile IEnlistmentNotification like this:

internal class SendResourceManager : IEnlistmentNotification
{
    private readonly Func<Task> onCommit;

    public SendResourceManager(Func<Task> onCommit)
    {
        this.onCommit = onCommit;
    }

    public void Prepare(PreparingEnlistment preparingEnlistment)
    {
        preparingEnlistment.Prepared();
    }

    public void Commit(Enlistment enlistment)
    {
        await this.onCommit();
        enlistment.Done();
    }

    public void Rollback(Enlistment enlistment)
    {
        enlistment.Done();
    }

    public void InDoubt(Enlistment enlistment)
    {
        enlistment.Done();
    }
}

and the new sender

public class MessageSender : ISendMessages
{
    public async Task SendAsync(TransportMessage message, SendOptions options)
    {
        // Dirty: Let's assume Transaction.Current is never null
        Transaction.Current.EnlistVolatile(new SendResourceManager(async () => { await sender.SendAsync(message) }));
    }
}

Note: Of course this code doesn't compile. It would require me to declare the commit method async void. Which is aweful.

So my question is: How can I write an enlistment which can internally await an asynchronous operation?

回答1:

As long as EnlistVolatile isn't a heavy CPU bound time consuming operation, you can create a thin Task based wrapper over EnlistVolatile using Task.FromResult:

public static class TranscationExtensions
{
    public static Task EnlistVolatileAsync(this Transaction transaction, 
                                           IEnlistmentNotification 
                                           enlistmentNotification, 
                                           EnlistmentOptions enlistmentOptions)
    {
        return Task.FromResult(transaction.EnlistVolatile
                                           (enlistmentNotification, 
                                            enlistmentOptions));
    }
}

and then consume it inside your method:

public class MessageSender : ISendMessages
{
    public Task SendAsync(TransportMessage message, SendOptions options)
    {
        return Transaction.Current.EnlistVolatileAsync
               (new SendResourceManager(async () => 
                                       { await sender.SendAsync(message) }));
    }
}

which can be awaited higher in your callstack:

MessageSender sender = new MessageSender();
await sender.SendAsync(message, options);