public static class TelegramBotClientPollingExtensions
{
    public static void StartReceiving(
        this ITelegramBotClient client,
        Func<Update, Task> updateHandler,
        Func<Exception, Task> exceptionHandler, // ApiRequestException, GeneralException, AS WELL AS exceptions from updateHandler - in that case stop receiving
        UpdateType[]? allowedUpdates = null,
        CancellationToken cancellationToken = default)
    {
        if (exceptionHandler == null)
            throw new ArgumentNullException(nameof(exceptionHandler));

        Task.Run(async () =>
        {
            try
            {
                await ReceiveAsync(client, updateHandler, exceptionHandler, allowedUpdates, cancellationToken);
            }
            catch (Exception ex)
            {
                await exceptionHandler(ex);
            }
        });
    }

    public static async Task ReceiveAsync(
        this ITelegramBotClient client,
        Func<Update, Task> updateHandler,
        Func<Exception, Task>? exceptionHandler = null, // ApiRequestException, GeneralException
        UpdateType[]? allowedUpdates = null,
        CancellationToken cancellationToken = default)
    {
        int messageOffset = 0;
        var emptyUpdates = new Update[0];

        while (!cancellationToken.IsCancellationRequested)
        {
            int timeout = (int)client.Timeout.TotalSeconds;
            var updates = emptyUpdates;
            try
            {
                updates = await client.GetUpdatesAsync(
                    messageOffset,
                    timeout: timeout,
                    allowedUpdates: allowedUpdates,
                    cancellationToken: cancellationToken
                ).ConfigureAwait(false);
            }
            catch (OperationCanceledException)
            {
                // Ignore
            }
            catch (Exception ex)
            {
                if (exceptionHandler != null)
                {
                    await exceptionHandler(ex).ConfigureAwait(false);
                }
            }

            foreach (var update in updates)
            {
                await updateHandler(update).ConfigureAwait(false);
                messageOffset = update.Id + 1;
            }
        }
    }
}

#if NETCOREAPP3_0
public static class TBD_YieldExtensions
{
    public static async IAsyncEnumerable<Update> YieldUpdatesAsync(
        this ITelegramBotClient client,
        Func<Exception, Task>? exceptionHandler = null, // ApiRequestException, GeneralException
        UpdateType[]? allowedUpdates = null,
        CancellationToken cancellationToken = default)
    {
        int messageOffset = 0;
        var emptyUpdates = new Update[0];

        while (!cancellationToken.IsCancellationRequested)
        {
            int timeout = (int)client.Timeout.TotalSeconds;
            var updates = emptyUpdates;
            try
            {
                updates = await client.GetUpdatesAsync(
                    messageOffset,
                    timeout: timeout,
                    allowedUpdates: allowedUpdates,
                    cancellationToken: cancellationToken
                ).ConfigureAwait(false);
            }
            catch (OperationCanceledException)
            {
                // Ignore
            }
            catch (Exception ex)
            {
                if (exceptionHandler != null)
                {
                    await exceptionHandler(ex).ConfigureAwait(false);
                }
            }

            foreach (var update in updates)
            {
                yield return update;
                messageOffset = update.Id + 1;
            }
        }
    }

    public static async IAsyncEnumerable<Update> YieldQueuedUpdatesAsync(
        this ITelegramBotClient client,
        Func<Exception, Task>? exceptionHandler = null, // ApiRequestException, GeneralException
        UpdateType[]? allowedUpdates = null,
        CancellationToken cancellationToken = default)
    {
        yield return new Update();
    }
}
#endif