def readinto(self, b):
        """
        Read bytes into a pre-allocated, writable bytes-like object b,
        and return the number of bytes read.

        Args:
            b (bytes-like object): buffer.

        Returns:
            int: number of bytes read
        """
        if not self._readable:
            raise UnsupportedOperation('read')

        with self._seek_lock:
            # Gets seek
            seek = self._seek

            # Initializes queue
            queue = self._read_queue
            if seek == 0:
                # Starts initial preloading on first call
                self._preload_range()

            # Initializes read data buffer
            size = len(b)
            if size:
                # Preallocated buffer:
                # Use memory view to avoid copies
                b_view = memoryview(b)
                size_left = size
            else:
                # Dynamic buffer:
                # Can't avoid copy, read until EOF
                b_view = b
                size_left = -1
            b_end = 0

            # Starts reading
            buffer_size = self._buffer_size
            while size_left > 0 or size_left == -1:

                # Finds buffer position in queue and buffer seek
                start = seek % buffer_size
                queue_index = seek - start

                # Gets preloaded buffer
                try:
                    buffer = queue[queue_index]
                except KeyError:
                    # EOF
                    break

                # Get buffer from future
                with handle_os_exceptions():
                    try:
                        queue[queue_index] = buffer = buffer.result()

                    # Already evaluated
                    except AttributeError:
                        pass
                buffer_view = memoryview(buffer)
                data_size = len(buffer)

                # Checks if end of file reached
                if not data_size:
                    break

                # Gets theoretical range to copy
                if size_left != -1:
                    end = start + size_left
                else:
                    end = data_size - start

                # Checks for end of buffer
                if end >= data_size:
                    # Adjusts range to copy
                    end = data_size

                    # Removes consumed buffer from queue
                    del queue[queue_index]

                    # Append another buffer preload at end of queue
                    index = queue_index + buffer_size * self._max_buffers
                    if index < self._size:
                        queue[index] = self._workers.submit(
                            self._read_range, index, index + buffer_size)

                # Gets read size, updates seek and updates size left
                read_size = end - start
                if size_left != -1:
                    size_left -= read_size
                seek += read_size

                # Defines read buffer range
                b_start = b_end
                b_end = b_start + read_size

                # Copy data from preload buffer to read buffer
                b_view[b_start:b_end] = buffer_view[start:end]

            # Updates seek and sync raw
            self._seek = seek
            self._raw.seek(seek)

        # Returns read size
        return b_end
Read bytes into a pre-allocated, writable bytes-like object b,
        and return the number of bytes read.

        Args:
            b (bytes-like object): buffer.

        Returns:
            int: number of bytes read