'Effectively streams data from multiple opened AFF4ImageBase objects.
Args:
fds: A list of opened AFF4Stream (or AFF4Stream descendants) objects.
Yields:
Tuples (chunk, fd, exception) where chunk is a binary blob of data and fd
is an object from the fds argument.
If one or more chunks are missing, exception will be a MissingChunksError
while chunk will be None. _MultiStream does its best to skip the file
entirely if one of its chunks is missing, but in case of very large files
it\'s still possible to yield a truncated file.'

@classmethod
def _MultiStream(cls, fds):

    missing_chunks_by_fd = {}
    for chunk_fd_pairs in utils.Grouper(cls._GenerateChunkPaths(fds), cls.MULTI_STREAM_CHUNKS_READ_AHEAD):
        chunks_map = dict(chunk_fd_pairs)
        contents_map = {}
        for chunk_fd in FACTORY.MultiOpen(chunks_map, mode='r', token=fds[0].token):
            if isinstance(chunk_fd, AFF4Stream):
                fd = chunks_map[chunk_fd.urn]
                contents_map[chunk_fd.urn] = chunk_fd.read()
        for (chunk_urn, fd) in chunk_fd_pairs:
            if ((chunk_urn not in contents_map) or (not contents_map[chunk_urn])):
                missing_chunks_by_fd.setdefault(fd, []).append(chunk_urn)
        for (chunk_urn, fd) in chunk_fd_pairs:
            if (fd in missing_chunks_by_fd):
                continue
            (yield (fd, contents_map[chunk_urn], None))
    for (fd, missing_chunks) in missing_chunks_by_fd.iteritems():
        e = MissingChunksError(('%d    missing    chunks    (multi-stream).' % len(missing_chunks)), missing_chunks=missing_chunks)
        (yield (fd, None, e))
