<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 async def _connect(self):
 """ Performs the actual connection, retrying, generating the authorization key if necessary, and starting the send and receive loops. """

        self._log.info('Connecting to %s...', self._connection)
        for attempt in retry_range(self._retries):
            try:
                self._log.debug('Connection attempt {}...'.format(attempt))
                await self._connection.connect(timeout=self._connect_timeout)
            except (IOError, asyncio.TimeoutError) as e:
                self._log.warning('Attempt {} at connecting failed: {}: {}'
                                  .format(attempt, type(e).__name__, e))
                await asyncio.sleep(self._delay)
            else:
                break
        else:
            raise ConnectionError('Connection to Telegram failed {} time(s)'
                                  .format(attempt))

        self._log.debug('Connection success!')
        if not self.auth_key:
            plain = MTProtoPlainSender(self._connection, loggers=self._loggers)
            for attempt in retry_range(self._retries):
                try:
                    self._log.debug('New auth_key attempt {}...'
                                    .format(attempt))
                    self.auth_key.key, self._state.time_offset =\
                        await authenticator.do_authentication(plain)

                    # This is *EXTREMELY* important since we don't control
                    # external references to the authorization key, we must
                    # notify whenever we change it. This is crucial when we
                    # switch to different data centers.
                    if self._auth_key_callback:
                        self._auth_key_callback(self.auth_key)

                    break
                except (SecurityError, AssertionError) as e:
                    self._log.warning('Attempt {} at new auth_key failed: {}'
                                      .format(attempt, e))
                    await asyncio.sleep(self._delay)
            else:
                e = ConnectionError('auth_key generation failed {} time(s)'
                                    .format(attempt))
                await self._disconnect(error=e)
                raise e

        self._log.debug('Starting send loop')
        self._send_loop_handle = self._loop.create_task(self._send_loop())

        self._log.debug('Starting receive loop')
        self._recv_loop_handle = self._loop.create_task(self._recv_loop())

        # _disconnected only completes after manual disconnection
        # or errors after which the sender cannot continue such
        # as failing to reconnect or any unexpected error.
        if self._disconnected.done():
            self._disconnected = self._loop.create_future()

        self._log.info('Connection to %s complete!', self._connection)