Rachel Rachel - 9 months ago 25
C# Question

How can I wait for my async operations to finish when the application gets exited using?

If a user performs an operation, such as deleting items, it removes them from the UI right away and then deletes them from the database on a background thread using TPL. The problem is if the user exits the application before the background thread finishes, the item never actually gets deleted.

Is there a standard way of waiting for async operations to finish before shutting down the application?

My async calls look like this:

if (MyObjectList.Contains(obj)) MyObjectList.Remove(obj);
Task.Factory.StartNew(() => DAL<MyEntities>.DeleteObject(obj));


Update

Here's the final code I went with. I'm quite happy to see it works as it should, although let me know if I can improve it. I still have a lot to learn :)

public partial class App : Application
{
private List<Task> _backgroundTasks = new List<Task>();

public App()
{
EventSystem.Subscribe<TaskStartedMessage>((e) =>
{
_backgroundTasks.Add(e.Task);
});

EventSystem.Subscribe<TaskEndedMessage>((e) =>
{
if (_backgroundTasks.Contains(e.Task))
_backgroundTasks.Remove(e.Task);
});
}

protected override void OnExit(ExitEventArgs e)
{
Task.WaitAll(_backgroundTasks.Where(p => !p.IsCompleted).ToArray(), 30000);

base.OnExit(e);
}
}


And when starting an important background task, I'm using this syntax:

var task = Task.Factory.StartNew(() => DAL<MyEntities>.DeleteObject(obj));
EventSystem.Publish<TaskStartedMessage>(new TaskStartedMessage(task));
await task;
EventSystem.Publish<TaskEndedMessage>(new TaskEndedMessage(task));


I'm using AsyncCTP for
await
/
async
, and Microsoft Prism's
EventAggregator
for the event system.

Answer Source

There is no standard way but since you create a specific Task here it should be easy to put that in a List and build some Exit-logic to Wait for all Tasks in that List.

OK, a sample. Untested and incomplete:

// untested
static class CriticalTasks
{
    static HashSet<Task> tasks = new HashSet<Task>();
    static object locker = new object();

    // when starting a Task
    public static void Add(Task t)
    {
        lock(locker)
           tasks.Add(t);
    }

    // When a Tasks completes
    public static void Remove(Task t)
    {
        lock(locker)
           tasks.Remove(t);
    }

    // Having to call Remove() is not so convenient, this is a blunt solution. 
    // call it regularly
    public static void Cleanup()
    {
        lock(locker)
           tasks.RemoveWhere(t => t.Status != TaskStatus.Running);
    }

    // from Application.Exit() or similar. 
    public static void WaitOnExit()
    {
        // filter, I'm not sure if Wait() on a canceled|completed Task would be OK
        var waitfor = tasks.Where(t => t.Status == TaskStatus.Running).ToArray();
        Task.WaitAll(waitfor, 5000);
    }
}


The drawback is that you will have to extend each Task with the code to Add & Remove it.

Forgetting a Remove() (eg when an Exception happens) would be a (small) memory-leak. It is not too critical, instead of burdening your code with using() blocks you could also periodically run a Cleanup() method that uses HashSet.RemoveWhere() to remove non-running tasks.