Reputation: 132548
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.
Upvotes: 15
Views: 7451
Reputation: 429
In lieu of a crappy code example. The answer is you simply cannot. If the application domain begins terminating you have a finite window to perform cleanup tasks before all such tasks are forcefully terminated. Your best course of action is to have some sort of job control that you statefully queue the work into. This will protect against abnormal termination as well.
Otherwise you simply do not start exiting until you perform the WaitAll and it fully completes.
Upvotes: 0
Reputation: 273179
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.
Upvotes: 10
Reputation: 11957
You could store reference to your Task, and wait for it when application is quitting (on Exit events for example).
You could also create a normal thread, make sure you set IsBackground to false
(default value). Process will not quit until all non-background threads will finish their work, so it will run to the end, you will have to watch out not to use any GUI logic or make sure that you will not dispose objects that you need from this thread.
Upvotes: 0
Reputation: 4907
var x = Task.Factory.StartNew(() => DAL<MyEntities>.DeleteObject(obj));
In form close event:
while(!x.IsCompleted){hide form}
Or
if(!x.IsCompleted)
//cancel exit
Upvotes: 0