10
votes

I have code that works precisely as desired. However, our corporate build server rejects any check-in that has a compiler warning.

The following warning is (as expected) displayed for the Action constructor with the Action to Func conversions, since I am not using an await statement.

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

public class TransactionOperation
{
    private readonly Func<Task> _operation;
    private readonly Func<Task> _rollback;

    public OperationStatus Status { get; private set; }

    public TransactionOperation(Func<Task> operation, Func<Task> rollback)
    {
        _operation = operation;
        _rollback = rollback;
        Status = OperationStatus.NotStarted;
    }

    public TransactionOperation(Action operation, Action rollback)
    {
        _operation = async () => operation.Invoke();
        _rollback = async () => rollback.Invoke();
        Status = OperationStatus.NotStarted;
    }

    public async Task Invoke()
    {
        try
        {
            Status = OperationStatus.InProcess;
            await _operation.Invoke();
            Status = OperationStatus.Completed;
        }
        catch (Exception ex)
        {
            //...
        }
    }
}

What is a correct way to rewrite that code so that the Action is correctly converted to Func without being executed yet or creating a new thread (i.e. await Task.Run())?


Update - Proposed Answer #1

_operation = () => new Task(operation.Invoke);

_rollback = () => new Task(rollback.Invoke);

I tried this before. It causes this unit test to never return.

[TestMethod, TestCategory("Unit Test")]
public async Task Transaction_MultiStepTransactionExceptionOnFourthAction_CorrectActionsRolledBack()
{
    var operation = new TransactionOperation(PerformAction, () => RollbackOperation(1));
    var operation2 = new TransactionOperation(PerformAction, () => RollbackOperation(2));
    var operation3 = new TransactionOperation(PerformAction, () => RollbackOperation(3));
    var operation4 = new TransactionOperation(ExceptionAction, () => RollbackOperation(4));
    var operation5 = new TransactionOperation(PerformAction, () => RollbackOperation(5));
    var transaction = new Transaction(new[] { operation, operation2, operation3, operation4, operation5 });

    await IgnoreExceptions(transaction.ExecuteAsync);

    AssertActionsPerformedThrough(4);
    AssertActionsRolledBackThrough(4);
}

Update - Accepted Answer

private async Task ConvertToTask(Action action)
{
    action.Invoke();
    await Task.FromResult(true);
}

Here's the updated Action constructor:

public TransactionOperation(Action operation, Action rollback)
{
    _operation = () => ConvertToTask(operation);
    _rollback = () => ConvertToTask(rollback);
    Status = OperationStatus.NotStarted;
}
2
Why are you doing async on a thing that will run synchronously?Luis Filipe
Well, async () => { operation.Invoke(); await Task.FromResult(0); } should do it, but the above comment is still relevant: why do you want to wrap synchronous code as asynchronous, in the first place?noseratio

2 Answers

12
votes

You can just use Task.FromResult(0):

_operation = async () => { operation.Invoke(); await Task.FromResult(0); };
2
votes

You mean like this:

_operation = () => new Task(operation.Invoke);
_rollback = () => new Task(rollback.Invoke);

The functions will then return a task with the desired action which have not yet been started.

I think you'll need to update your Invoke method to the below to get the behaviour you're looking for:

Status = OperationStatus.InProcess;
await Task.Run(() =>
    {
        _operation().Start();
        _operation().Wait();
    });
Status = OperationStatus.Completed;