'Effectively streams data from multiple opened BlobImage 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 is a MissingBlobsError object
and chunk is 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):

    broken_fds = set()
    missing_blobs_fd_pairs = []
    for chunk_fd_pairs in utils.Grouper(cls._GenerateChunkIds(fds), cls.MULTI_STREAM_CHUNKS_READ_AHEAD):
        results_map = data_store.DB.ReadBlobs(dict(chunk_fd_pairs).keys(), token=fds[0].token)
        for (chunk_id, fd) in chunk_fd_pairs:
            if ((chunk_id not in results_map) or (results_map[chunk_id] is None)):
                missing_blobs_fd_pairs.append((chunk_id, fd))
                broken_fds.add(fd)
        for (chunk, fd) in chunk_fd_pairs:
            if (fd in broken_fds):
                continue
            (yield (fd, results_map[chunk], None))
    if missing_blobs_fd_pairs:
        missing_blobs_by_fd = {}
        for (chunk_id, fd) in missing_blobs_fd_pairs:
            missing_blobs_by_fd.setdefault(fd, []).append(chunk_id)
        for (fd, missing_blobs) in missing_blobs_by_fd.iteritems():
            e = MissingBlobsError(('%d    missing    blobs    (multi-stream)' % len(missing_blobs)), missing_chunks=missing_blobs)
            (yield (fd, None, e))
