private async Task SendBatch() { batchId++; if (batchId >= long.MaxValue) { batchId = 1; } // Pause the timer timerBatchWait.Change(Timeout.Infinite, Timeout.Infinite); if (notifications.Count <= 0) { return; } // Let's store the batch items to send internally var toSend = new BlockingCollection <CompletableApnsNotification>(); while (notifications.Count > 0 && toSend.Count < Configuration.InternalBatchSize) { CompletableApnsNotification n; if (notifications.TryDequeue(out n)) { toSend.Add(n); } } Log.Info("APNS-Client[{0}]: Sending Batch ID={1}, Count={2}", id, batchId, toSend.Count); try { var data = createBatch(toSend); if (data != null && data.Length > 0) { for (var i = 0; i <= Configuration.InternalBatchFailureRetryCount; i++) { await connectingSemaphore.WaitAsync(); try { // See if we need to connect if (!socketCanWrite() || i > 0) { await connect(); } } finally { connectingSemaphore.Release(); } try { await networkStream.WriteAsync(data, 0, data.Length).ConfigureAwait(false); break; } catch (Exception ex) when(i != Configuration.InternalBatchFailureRetryCount) { Log.Info("APNS-CLIENT[{0}]: Retrying Batch: Batch ID={1}, Error={2}", id, batchId, ex); } } foreach (var n in toSend) { // If the notification failed validation before being sent (i.e. it was skipped) if (n.State == NotificationState.Failed) { continue; } sent.Add(new SentNotification(n)); } } } catch (Exception ex) { Log.Error("APNS-CLIENT[{0}]: Send Batch Error: Batch ID={1}, Error={2}", id, batchId, ex); foreach (var n in toSend) { n.CompleteFailed(new ApnsNotificationException(ApnsNotificationErrorStatusCode.ConnectionError, n.Notification, ex)); } var errorNotificationToSend = new List <CompletableApnsNotification>(); //Check to see if any notification items have a bad registration id foreach (var notificationItem in toSend) { if (!notificationItem.Notification.IsDeviceRegistrationIdValid()) { errorNotificationToSend.Add(notificationItem); } } if (errorNotificationToSend.Count > 0) { //If any devices had a bad registration id assume this exception was caused by those bad ids and requeue the other notifications foreach (var notificationItem in toSend.Except(errorNotificationToSend)) { notifications.Enqueue(notificationItem); } //Report invalid token errors for each invalid registration id foreach (var n in errorNotificationToSend) { n.CompleteFailed(new ApnsNotificationException(ApnsNotificationErrorStatusCode.InvalidToken, n.Notification, ex)); } } else { //If there were no invalid registration ids then report the errors as normal foreach (var n in toSend) { n.CompleteFailed(new ApnsNotificationException(ApnsNotificationErrorStatusCode.ConnectionError, n.Notification, ex)); } } } Log.Info("APNS-Client[{0}]: Sent Batch, waiting for possible response...", id); try { await Reader(); } catch (Exception ex) { Log.Error("APNS-Client[{0}]: Reader Exception: {1}", id, ex); } Log.Info("APNS-Client[{0}]: Done Reading for Batch ID={1}, reseting batch timer...", id, batchId); // Restart the timer for the next batch timerBatchWait.Change(Configuration.InternalBatchingWaitPeriod, Timeout.InfiniteTimeSpan); }