0

I have the following abstract base class that all tasks inherit from.

public abstract class ScheduledTaskBase : ITask
{
    private readonly ILogRecorder _logRecorder;
    protected ScheduledTaskBase(ILogRecorder logRecorder)
    {
        _logRecorder = logRecorder;
    }

    protected IConfigurationRoot Configuration { get; private set; }

    public void Run()
    {
        OnRun();
        _logRecorder.LogProcessRunDate(TaskType);
    }
    
    protected abstract ScheduledTask TaskType { get; }
    protected abstract void OnRun();
}

This is one of the inheriting class.

public class FailedOrderTask : ScheduledTaskBase
{
    protected override ScheduledTask TaskType => ScheduledTask.FailedOrderTask;

    private readonly IService _service;
    public FailedOrderTask(IService service, ILogRecorder logRecorder) : base(logRecorder)
    {
        _service = service;
    }

    protected override void OnRun()
    {
        Task.Run(() => { _service.ProcessFailedOrders(); }).Wait();
    }
}

Here is the IService's ProcessFailedOrders method.

public async Task ProcessFailedOrders()
{
    var orders = await _repository.GetFailedOrders();
    foreach (var order in orders)
        await PlaceOrder(order);
}

And the repository's GetFailedOrders method.

public async Task<List<Order>> GetFailedOrders()
{
    return await _context.Orders.Where(x => !x.DateOrdered.HasValue).ToListAsync();
}

When the task runs and gets into the repository method, it then jumps back out to the ScheduledTaskBase Run method's _logRecorder.LogProcessRunDate line. This causes a 2nd operation started on this context error. If I put a breakpoint on the _logRecorder line and then move to the next line - the closing curly brace, it then jumps back to the repository and returns the data to the service.

We have a number of other tasks that are set up the same way where they do something like the following and they work fine.

Task.Run(() => SomeClass.Method).Wait()

So I can't figure out why this one task always jumps out of the async method only to come back if I don't let the _logRecorder line execute.

geoff swartz
  • 5,437
  • 11
  • 51
  • 75
  • 1
    In your `protected override void OnRun()` method, you're not awaiting the async task. I think it's jumping right to the log without waiting for the result. Edit: Would `await _service.ProcessFailedOrders()` work there? – bigcrazyal Jun 09 '23 at 14:29
  • 5
    You wrote `{ _service.ProcessFailedOrders(); }` instead of `=> _service.ProcessFailedOrders` as you mention you use it at other locations in your code. Is that intentional? You might want to look at other questions like https://stackoverflow.com/questions/18013523/when-correctly-use-task-run-and-when-just-async-await – Progman Jun 09 '23 at 14:35
  • Progman, that was it! Thank you. I didn't realize surrounding that call with curly braces would cause the problem. Removed them and it works fine now. – geoff swartz Jun 09 '23 at 15:50

1 Answers1

2

Both @bigcrazyal and @progman are correct. I'll just elaborate a bit why it happens. This line

Task.Run(() => { _service.ProcessFailedOrders(); }).Wait();

Is equivalent to

Task.Run(() =>
{
    _service.ProcessFailedOrders();
}).Wait();

Note the Task returned by ProcessFailedOrders is not returned and is not awaited. Hence you are using the Task.Run(Action action) overload, which completes immediately after the ProcessFailedOrdersis called. You need to use the Task.Run(Func<Task> task) overload.

You can fix it like this

Task.Run(async () => { await _service.ProcessFailedOrders(); }).Wait();

Which is equivalent to

Task.Run(async () =>
{
    // the Task is awaited now
    await _service.ProcessFailedOrders();
}).Wait();

Or like this

Task.Run(() => _service.ProcessFailedOrders()).Wait();

Which is equivalent to

Task.Run(() =>
{
    // the Task is returned now
    return _service.ProcessFailedOrders();
}).Wait();

Or like this

Task.Run(_service.ProcessFailedOrders).Wait();
Artur
  • 4,595
  • 25
  • 38