/// <summary>
 ///   Reads events from the requested partition as an asynchronous enumerable, allowing events to be iterated as they
 ///   become available on the partition, waiting as necessary should there be no events available.
 ///
 ///   This version of the enumerator may block for an indeterminate amount of time for an <c>await</c> if events are not available
 ///   on the partition, requiring cancellation via the <paramref name="cancellationToken"/> to be requested in order to return control.
 ///   It is recommended to call the overload which accepts a maximum wait time for scenarios where a more deterministic wait period is
 ///   desired.
 /// </summary>
 ///
 /// <param name="partitionId">The identifier of the Event Hub partition from which events will be received.</param>
 /// <param name="startingPosition">The position within the partition where the consumer should begin reading events.</param>
 /// <param name="cancellationToken">An optional <see cref="CancellationToken"/> instance to signal the request to cancel the operation.</param>
 ///
 /// <returns>An <see cref="IAsyncEnumerable{T}"/> to be used for iterating over events in the partition.</returns>
 ///
 /// <remarks>
 ///   Each reader of events is presented with an independent iterator; if there are multiple readers, each receive their own copy of an event to
 ///   process, rather than competing for them.
 /// </remarks>
 ///
 /// <seealso cref="ReadEventsFromPartitionAsync(string, EventPosition, TimeSpan?, CancellationToken)"/>
 ///
 public virtual IAsyncEnumerable <PartitionEvent> ReadEventsFromPartitionAsync(string partitionId,
                                                                               EventPosition startingPosition,
                                                                               CancellationToken cancellationToken = default) => ReadEventsFromPartitionAsync(partitionId, startingPosition, null, cancellationToken);
示例#2
0
        /// <summary>
        ///   Publishes events for the requested <paramref name="partitionId"/> to the provided
        ///   <paramref name="channel" /> in the background, using a dedicated transport consumer
        ///   instance.
        /// </summary>
        ///
        /// <param name="partitionId">The identifier of the partition from which events should be read.</param>
        /// <param name="startingPosition">The position within the partition's event stream that reading should begin from.</param>
        /// <param name="channel">The channel to which events should be published.</param>
        /// <param name="publishingCancellationSource">A cancellation source which can be used for signaling publication to stop.</param>
        ///
        /// <returns>A function to invoke when publishing should stop; once complete, background publishing is no longer taking place and the <paramref name="channel"/> has been marked as final.</returns>
        ///
        /// <remarks>
        ///   This method assumes co-ownership of the <paramref name="channel" />, marking its writer as completed
        ///   when publishing is complete or when an exception is encountered.
        ///
        ///   This method also assumes co-ownership of the <paramref name="publishingCancellationSource" /> and will request cancellation
        ///   from it when publishing is complete or when an exception is encountered.
        /// </remarks>
        ///
        private async Task <Func <Task> > PublishPartitionEventsToChannelAsync(string partitionId,
                                                                               EventPosition startingPosition,
                                                                               Channel <PartitionEvent> channel,
                                                                               CancellationTokenSource publishingCancellationSource)
        {
            publishingCancellationSource.Token.ThrowIfCancellationRequested <TaskCanceledException>();
            EventHubsEventSource.Log.PublishPartitionEventsToChannelStart(EventHubName, partitionId);

            var transportConsumer = default(TransportConsumer);
            var publishingTask    = default(Task);
            var observedException = default(Exception);
            var publisherId       = Guid.NewGuid().ToString();

            // Termination must take place in multiple places due to the a catch block being
            // disallowed with the use of "yield return".  Create a local function that encapsulates
            // the cleanup tasks needed.

            async Task performCleanup()
            {
                publishingCancellationSource?.Cancel();

                if (publishingTask != null)
                {
                    try
                    {
                        await publishingTask.ConfigureAwait(false);
                    }
                    catch (TaskCanceledException)
                    {
                        // This is an expected scenario; no action is needed.
                    }
                }

                if (transportConsumer != null)
                {
                    await transportConsumer.CloseAsync(CancellationToken.None).ConfigureAwait(false);

                    ActiveConsumers.TryRemove(publisherId, out var _);
                }

                publishingCancellationSource?.Dispose();
                channel.Writer.TryComplete();

                try
                {
                    if (observedException != default)
                    {
                        EventHubsEventSource.Log.PublishPartitionEventsToChannelError(EventHubName, partitionId, observedException.Message);
                        throw observedException;
                    }
                }
                finally
                {
                    EventHubsEventSource.Log.PublishPartitionEventsToChannelComplete(EventHubName, partitionId);
                }
            }

            // Setup the publishing context and begin publishing to the channel in the background.

            try
            {
                transportConsumer = Connection.CreateTransportConsumer(ConsumerGroup, partitionId, startingPosition, Options);

                if (!ActiveConsumers.TryAdd(publisherId, transportConsumer))
                {
                    await transportConsumer.CloseAsync(CancellationToken.None).ConfigureAwait(false);

                    transportConsumer = null;
                    throw new EventHubsException(false, EventHubName, String.Format(CultureInfo.CurrentCulture, Resources.FailedToCreateReader, EventHubName, partitionId, ConsumerGroup));
                }

                publishingTask = StartBackgroundChannelPublishingAsync
                                 (
                    transportConsumer,
                    channel,
                    new PartitionContext(partitionId, transportConsumer),
                    ex => { observedException = ex; },
                    publishingCancellationSource.Token
                                 );
            }
            catch (Exception ex)
            {
                EventHubsEventSource.Log.PublishPartitionEventsToChannelError(EventHubName, partitionId, ex.Message);
                await performCleanup().ConfigureAwait(false);

                throw;
            }

            return(performCleanup);
        }
 /// <summary>
 ///   Initializes a new instance of the <see cref="EventHubConsumerClient"/> class.
 /// </summary>
 ///
 /// <param name="consumerGroup">The name of the consumer group this consumer is associated with.  Events are read in the context of this group.</param>
 /// <param name="partitionId">The identifier of the Event Hub partition from which events will be received.</param>
 /// <param name="eventPosition">The position within the partition where the consumer should begin reading events.</param>
 /// <param name="connectionString">The connection string to use for connecting to the Event Hubs namespace; it is expected that the Event Hub name and the shared key properties are contained in this connection string.</param>
 ///
 /// <remarks>
 ///   If the connection string is copied from the Event Hubs namespace, it will likely not contain the name of the desired Event Hub,
 ///   which is needed.  In this case, the name can be added manually by adding ";EntityPath=[[ EVENT HUB NAME ]]" to the end of the
 ///   connection string.  For example, ";EntityPath=telemetry-hub".
 ///
 ///   If you have defined a shared access policy directly on the Event Hub itself, then copying the connection string from that
 ///   Event Hub will result in a connection string that contains the name.
 /// </remarks>
 ///
 public EventHubConsumerClient(string consumerGroup,
                               string partitionId,
                               EventPosition eventPosition,
                               string connectionString) : this(consumerGroup, partitionId, eventPosition, connectionString, null, null)
 {
 }
示例#4
0
        /// <summary>
        ///   Reads events from the requested partition as an asynchronous enumerable, allowing events to be iterated as they
        ///   become available on the partition, waiting as necessary should there be no events available.
        ///
        ///   If the <paramref name="maximumWaitTime" /> is passed, if no events were available before the wait time elapsed, an empty
        ///   item will be sent in the enumerable in order to return control and ensure that <c>await</c> calls do not block for an
        ///   indeterminate length of time.
        /// </summary>
        ///
        /// <param name="partitionId">The identifier of the Event Hub partition from which events will be received.</param>
        /// <param name="startingPosition">The position within the partition where the consumer should begin reading events.</param>
        /// <param name="maximumWaitTime">The maximum amount of time to wait to for an event to be available before emitting an empty item; if <c>null</c>, empty items will not be published.</param>
        /// <param name="cancellationToken">An optional <see cref="CancellationToken"/> instance to signal the request to cancel the operation.</param>
        ///
        /// <returns>An <see cref="IAsyncEnumerable{T}"/> to be used for iterating over events in the partition.</returns>
        ///
        /// <remarks>
        ///   Each reader of events is presented with an independent iterator; if there are multiple readers, each receive their own copy of an event to
        ///   process, rather than competing for them.
        /// </remarks>
        ///
        /// <seealso cref="ReadEventsFromPartitionAsync(string, EventPosition, CancellationToken)"/>
        ///
        public virtual async IAsyncEnumerable <PartitionEvent> ReadEventsFromPartitionAsync(string partitionId,
                                                                                            EventPosition startingPosition,
                                                                                            TimeSpan?maximumWaitTime,
                                                                                            [EnumeratorCancellation] CancellationToken cancellationToken = default)
        {
            Argument.AssertNotClosed(IsClosed, nameof(EventHubConsumerClient));
            cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>();

            EventHubsEventSource.Log.ReadEventsFromPartitionStart(EventHubName, partitionId);

            var cancelPublishingAsync = default(Func <Task>);
            var eventChannel          = default(Channel <PartitionEvent>);

            using var cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);

            try
            {
                eventChannel          = CreateEventChannel(Math.Min((Options.PrefetchCount / 4), (BackgroundPublishReceiveBatchSize * 2)));
                cancelPublishingAsync = await PublishPartitionEventsToChannelAsync(partitionId, startingPosition, eventChannel, cancellationSource).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                EventHubsEventSource.Log.ReadEventsFromPartitionError(EventHubName, partitionId, ex.Message);
                await cancelPublishingAsync().ConfigureAwait(false);

                EventHubsEventSource.Log.ReadEventsFromPartitionComplete(EventHubName, partitionId);
                throw;
            }

            // Iterate the events from the channel.

            try
            {
                await foreach (var partitionEvent in eventChannel.Reader.EnumerateChannel(maximumWaitTime, cancellationToken).ConfigureAwait(false))
                {
                    yield return(partitionEvent);
                }
            }
            finally
            {
                cancellationSource?.Cancel();
                await cancelPublishingAsync().ConfigureAwait(false);

                EventHubsEventSource.Log.ReadEventsFromPartitionComplete(EventHubName, partitionId);
            }

            // If cancellation was requested, then surface the expected exception.

            cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>();
        }