Volatile IEnlistmentNotification, TransactionScope.AsyncFlowEnabled = true and complex async/wait

孤街醉人 提交于 2019-12-13 05:16:28

问题


This is a followup question to the following question:

Volatile IEnlistmentNotification and TransactionScope.AsyncFlowEnabled = true

The approach accepted in the question above works as long as you don't await multiple statements. Let me show an example:

public class SendResourceManager : IEnlistmentNotification
{
    private readonly Action onCommit;

    public SendResourceManager(Action onCommit)
    {
        this.onCommit = onCommit;
    }

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

    public void Commit(Enlistment enlistment)
    {
        Debug.WriteLine("Committing");
        this.onCommit();
        Debug.WriteLine("Committed");
        enlistment.Done();
    }

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

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

public class AsyncTransactionalMessageSender : ISendMessagesAsync
{
    private readonly List<Message> sentMessages = new List<Message>();

    public IReadOnlyCollection<Message> SentMessages
    {
        get { return new ReadOnlyCollection<Message>(this.sentMessages); }
    }

    public async Task SendAsync(Message message)
    {
        if (Transaction.Current != null)
        {
            await Transaction.Current.EnlistVolatileAsync(
                new SendResourceManager(async () => await this.SendInternal(message)), 
                EnlistmentOptions.None);
        }
        else
        {
            await this.SendInternal(message);
        }
    }

    private async Task SendInternal(Message message)
    {
        Debug.WriteLine("Sending");
        await Task.Delay(1000);
        this.sentMessages.Add(message);
        Debug.WriteLine("Sent");
    }
}

    [Test]
    public async Task ScopeRollbackAsync_DoesntSend()
    {
        var sender = new AsyncTransactionalMessageSender();
        using (var tx = new System.Transactions.TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
        {
            await sender.SendAsync(new Message("First"));
            await sender.SendAsync(new Message("Second"));
            await sender.SendAsync(new Message("Last"));

            // We do not commit the scope
        }

        sender.SentMessages.Should().BeEmpty();
    }

    [Test]
    public async Task ScopeCompleteAsync_Sends()
    {
        var sender = new AsyncTransactionalMessageSender();
        using (var tx = new System.Transactions.TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
        {
            await sender.SendAsync(new Message("First"));
            await sender.SendAsync(new Message("Second"));
            await sender.SendAsync(new Message("Last"));

            tx.Complete();
        }

        sender.SentMessages.Should().HaveCount(3)
            .And.Contain(m => m.Value == "First")
            .And.Contain(m => m.Value == "Second")
            .And.Contain(m => m.Value == "Last");
    }

As soon as you introduce a Task.Delay like shown in the example above the generated asynchronous statemachine will never come back and invoke the this.sentMessages.Add(message) and Debug.WriteLine("Sent")

The problem is I currently see now way to properly enlist asynchronous code inside the enlistment notification. Any ideas how to tackle this challenge?

来源:https://stackoverflow.com/questions/27376853/volatile-ienlistmentnotification-transactionscope-asyncflowenabled-true-and-c

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!