Actually run the application

        :param loop: Custom event loop or None for default

def run(self, loop=None):
        """Actually run the application

        :param loop: Custom event loop or None for default
        """
        if loop is None:
            loop = asyncio.get_event_loop()

        self.loop = loop

        loop.run_until_complete(self.startup())

        for func in self.tasks:
            self.start_task(func)

        try:
            task = self.start_task(self.main_task)
            loop.run_until_complete(task)
        except (KeyboardInterrupt, SystemError):
            print("Attempting graceful shutdown, press Ctrl-C again to exit", flush=True)

            def shutdown_exception_handler(_loop, context):
                if "exception" not in context or not isinstance(context["exception"], asyncio.CancelledError):
                    _loop.default_exception_handler(context)
            loop.set_exception_handler(shutdown_exception_handler)

            tasks = asyncio.gather(*self._started_tasks, loop=loop, return_exceptions=True)
            tasks.add_done_callback(lambda _: loop.stop())
            tasks.cancel()

            while not tasks.done() and not loop.is_closed():
                loop.run_forever()
        finally:
            loop.run_until_complete(self.shutdown())
            loop.run_until_complete(self.cleanup())
            loop.close()