Guy Lowe
Guy Lowe

Reputation: 2370

Event based Task framework for C# completing before process complete

I am trying to create a system that processes events as they come in as separate Tasks. This is all inside a Windows Service application that runs until it is stopped, and when stopped, waits until all Tasks complete before stopping.

I have the following, simplified, code:

IList<Task> _tasks = new List<Task>();//this is a private class variable.

private void Event_Fired(object sender, eventArgs e)
{
    StartTask();
}

private void StartTask(){
    Task task = Task.Factory.StartNew(async () => await process.ProcessEvent(true, StoppingToken));
   _tasks.Add(task);
}

private void MonitorTasks(){
    for (int i = _tasks.Count - 1; i >= 0; i--)
    {
        Task aTask = _laneTasks[i];
        if (aTask.IsCompleted)
        {
            _tasks.Remove(aTask);
            Log.Information($"Task {aTask.Id} completed.");
        }
    }
}

public async Task ProcessEvent(){
    //do some async processing
    await Task.Delay(5000); //if I add a delay before returning the Task, it is completed prior to this delay.
}

The issue I'm having is that the Task is completing before ProcessEvent has completed. I need to ensure the tasks are completed before allowing the process to stop.

The process also has to allow new events to enter and spawn new Tasks at anytime.

I feel like I'm missing something fundamental here. Any help would be appreciated.

Upvotes: 0

Views: 532

Answers (1)

Corey
Corey

Reputation: 16574

Task.Factory.StartNew(...) creates a new Task that executes the supplied delegate. The concrete type of the returned Task will depend on the return type of the delegate.

So let's look at the delegate you're passing in:

async () => await process.ProcessEvent(true, StoppingToken)

OK, that's a Func<Task>. Invoking the function will create a new Task and return it. So your Task<Task> completes immediately, with the Result value set to the inner Task object.

To complicate matters a little further, ProcessEvent() itself creates a Task that is, ultimately, what you want to wait on. So you're creating a Task to create a Task to wait on a Task. Of course the outer-most Task finishes as soon as the waiter Task is created, since its job is now done.

Since your code already does manual waiting across a collection of Task objects, you don't need a Task whose whole purpose is to wait for the innermost Task to complete. And since you're already returning a Task from ProcessEvent() you don't need a Task to create a Task.

Do this instead:

_tasks.Add(ProcessEvent());

Upvotes: 3

Related Questions