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;
}
You can just use Task.FromResult(0)
:
_operation = async () => { operation.Invoke(); await Task.FromResult(0); };
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;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With