Подтвердить что ты не робот

Как я могу дождаться завершения операций async при выходе из приложения?

Если пользователь выполняет операцию, например удаление элементов, он сразу удаляет их из пользовательского интерфейса, а затем удаляет их из базы данных в фоновом потоке с помощью TPL. Проблема заключается в том, что пользователь выходит из приложения до завершения фонового потока, элемент никогда не удаляется.

Есть ли стандартный способ ожидания завершения операций асинхронного доступа до закрытия приложения?

Мои асинхронные вызовы выглядят следующим образом:

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

Обновление

Вот последний код, с которым я пошел. Я очень рад видеть, что это работает так, как должно, хотя дайте мне знать, смогу ли я его улучшить. Мне еще многое предстоит узнать:)

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);
    }
}

И при запуске важной фоновой задачи я использую этот синтаксис:

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

Я использую AsyncCTP для await/async и Microsoft Prism EventAggregator для системы событий.

4b9b3361

Ответ 1

Нет стандартного способа, но поскольку вы создаете конкретную задачу здесь, это должно быть легко помещать в список и строить некоторую Exit-логику для ожидания всех задач в этом списке.

OK, образец. Непроверенный и неполный:

// 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);
    }
}


Недостатком является то, что вам нужно будет продлить каждую задачу с помощью кода для добавления и удаления.

Забывание Remove() (например, при возникновении исключения) будет (небольшая) утечка памяти. Это не слишком важно, вместо того чтобы обременять ваш код блоками using(), вы также можете периодически запускать метод Cleanup(), который использует HashSet.RemoveWhere() для удаления неисполняемых задач.

Ответ 2

Вы можете сохранить ссылку на свою задачу и дождаться ее, когда приложение прекратит работу (например, в случае выхода).

Вы также можете создать нормальный поток, убедитесь, что вы установите IsBackground на false (значение по умолчанию). Процесс не прекратится до тех пор, пока все нефонические потоки не закончат свою работу, поэтому он будет работать до конца, вам придется следить за тем, чтобы не использовать какую-либо логику графического интерфейса или не использовать объекты, которые вам нужны из этой темы.

Ответ 3

var x = Task.Factory.StartNew(() => DAL<MyEntities>.DeleteObject(obj));

В событии закрытия формы:

while(!x.IsCompleted){hide form}

или

if(!x.IsCompleted)
   //cancel exit

Ответ 4

Вместо примера crappy code. Ответ в том, что вы просто не можете. Если домен приложения заканчивается, у вас есть конечное окно для выполнения задач очистки до того, как все такие задачи будут принудительно прекращены. Ваш лучший способ действий состоит в том, чтобы иметь какой-то контроль над работой, за который вы с успехом ставите очередь в работу. Это также защитит от аномального завершения.

В противном случае вы просто не начинаете выходить, пока не будете выполнять WaitAll, и он будет полностью завершен.