/// <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);
/// <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) { }
/// <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>(); }