Create and schedule a set of asynchronous tasks.

    The function creates the tasks using a given list of agent addresses and
    wraps each of them in :func:`asyncio.ensure_future`. The ``*args`` and
    ``**kwargs`` are passed down to :func:`task_coro` when creating tasks for
    each address in :attr:`addrs`.

    Usage example for a method in a class derived from
    :class:`~creamas.mp.MultiEnvironment`::

        async def my_method(self, *args, **kwargs):
            async def task(addr, *args, **kwargs):
                r_manager = await self.env.connect(addr)
                return await r_manager.my_method(*args, **kwargs)

            return await util.create_tasks(task, self.addrs, *args, **kwargs)

    :param task_coro:
        Coroutine which is used for each address in :attr:`addrs`. The
        coroutine should accept an agent address as the first parameter.
    :param list addrs:
        A list of agent addresses used as the first parameters of
        :func:`task_coro`.
    :param bool flatten:
        If ``True`` the returned results are flattened into one list if the
        tasks return iterable objects. The parameter does nothing if all the
        results are not iterable.
    :returns:
        An awaitable coroutine which returns the results of tasks as a list or
        as a flattened list

def create_tasks(task_coro, addrs, *args, flatten=True, **kwargs):
    '''Create and schedule a set of asynchronous tasks.

    The function creates the tasks using a given list of agent addresses and
    wraps each of them in :func:`asyncio.ensure_future`. The ``*args`` and
    ``**kwargs`` are passed down to :func:`task_coro` when creating tasks for
    each address in :attr:`addrs`.

    Usage example for a method in a class derived from
    :class:`~creamas.mp.MultiEnvironment`::

        async def my_method(self, *args, **kwargs):
            async def task(addr, *args, **kwargs):
                r_manager = await self.env.connect(addr)
                return await r_manager.my_method(*args, **kwargs)

            return await util.create_tasks(task, self.addrs, *args, **kwargs)

    :param task_coro:
        Coroutine which is used for each address in :attr:`addrs`. The
        coroutine should accept an agent address as the first parameter.
    :param list addrs:
        A list of agent addresses used as the first parameters of
        :func:`task_coro`.
    :param bool flatten:
        If ``True`` the returned results are flattened into one list if the
        tasks return iterable objects. The parameter does nothing if all the
        results are not iterable.
    :returns:
        An awaitable coroutine which returns the results of tasks as a list or
        as a flattened list
    '''
    tasks = []
    for agent_addr in addrs:
        task = asyncio.ensure_future(task_coro(agent_addr, *args, **kwargs))
        tasks.append(task)
    return wait_tasks(tasks, flatten)