Как я могу дождаться завершения моих асинхронных операций при выходе из приложения с помощью? - PullRequest
12 голосов
/ 23 декабря 2011

Если пользователь выполняет операцию, такую ​​как удаление элементов, он сразу удаляет их из пользовательского интерфейса, а затем удаляет их из базы данных в фоновом потоке, используя 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 для системы событий.

Ответы [ 4 ]

10 голосов
/ 23 декабря 2011

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

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

// 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 () для удаления не запущенных задач.

0 голосов
/ 19 июля 2014

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

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

0 голосов
/ 23 декабря 2011
var x = Task.Factory.StartNew(() => DAL<MyEntities>.DeleteObject(obj));

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

while(!x.IsCompleted){hide form}

Или

if(!x.IsCompleted)
   //cancel exit
0 голосов
/ 23 декабря 2011

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

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

...