Q:

Make asynchronous multi tasks on multi delegated hosts

I make X times an action delegated on the group of hosts. In this particular case, it drains a server on all sockets (aka instances) of all of my haproxies:
- name: HAproxy drain node
  haproxy:
    state: drain
    host: '{{ inventory_hostname }}'
    socket: "{{ item[1] }}"
    backend: "{{ backend_name | default(omit) }}"
    wait: yes
  delegate_to: "{{ item[0] }}"
  loop: "{{ groups[lb_haproxy]|product(socketstats.stdout_lines)|list }}"
  register: lbmemberstatus

It works but it's sequential. So I try to apply it the pattern "fire and forget" and wait : an async task with pool=0 and a second one async_status which loops on the results of the first task. 
But I wasn't successful at getting it to work, in my case :

the async jobs are distributed on all delegated hosts. So I wait for each these delegated hosts (first loop on the async_status task).
And i wait for each jobs, getting it by looping on  lbmemberstatus.results. But in this list of jobs, jobs come from different delegated hosts.

- name: HAproxy drain node
  haproxy:
    state: drain
    host: '{{ inventory_hostname }}'
    socket: "{{ item[1] }}"
    backend: "{{ backend_name | default(omit) }}"
    wait: yes
    wait_retries: 12
  delegate_to: "{{ item[0] }}"
  loop: "{{ groups[lb_haproxy]|product(socketstats.stdout_lines)|list }}"
  register: lbmemberstatus
  async: 1000  # Maximum runtime in seconds.
  poll: 0  # Fire and continue (never poll)

- name: Wait for drain on all Haproxies
  async_status:
    jid: "{{ item[1].ansible_job_id }}"
  register: lbmemberstatus_poll_results
  until: lbmemberstatus_poll_results.finished
  retries: 300
  delegate_to: "{{ item[0] }}"
  loop: "{{ groups[lb_haproxy]|product(lbmemberstatus.results)|list }}"

Obviously, this example fails when the delegated hosts doesn't match to the host which runs the job. 
How would you do it ?

A:

You should iterate over lbmemberstatus.results but not construct a new product of groups[lb_haproxy] and lbmemberstatus.results which can of course mix up.
Registered result of a loop (in your case lbmemberstatus.results) always contains original item that was used for that particular iteration.
Try:
- debug:
    msg: "Host: {{ item.item[0], JobId: {{ item.ansible_job_id }}"
  loop: "{{ lbmemberstatus.results }}"

Here we have item as element of lbmemberstatus.results and item.item as element used for previous loop.

