public virtual async Task <ScheduledCallbackState> ScheduleCallbackAsync(ScheduledCallback callback)
        {
            // the next execution time is computed from the time the current method is called, as the
            // caller may hang on to the ScheduledCallback for some time before calling the current method.
            // we set the time here, before adding it to the collection below, so that any callback
            // in the collection will certainly have a next execution time.
            callback.NextExecution = DateTime.Now + callback.Delay;

            if (callback.State != ScheduledCallbackState.Created)
            {
                SensusException.Report("Attemped to schedule callback " + callback.Id + ", which is in the " + callback.State + " state and not the " + ScheduledCallbackState.Created + " state.");
                callback.State = ScheduledCallbackState.Unknown;
            }
            else if (_idCallback.TryAdd(callback.Id, callback))
            {
                callback.InvocationId = Guid.NewGuid().ToString();

                BatchNextExecutionWithToleratedDelay(callback);

                // the state needs to be updated after batching is performed, so that other callbacks don't
                // attempt to batch with it, but before the invocations are requested, so that if the invocation
                // comes back immediately (e.g., being scheduled in the past) the callback is scheduled and
                // ready to run.
                callback.State = ScheduledCallbackState.Scheduled;

                await RequestLocalInvocationAsync(callback);
            }
            else
            {
                SensusServiceHelper.Get().Logger.Log("Attempted to schedule duplicate callback for " + callback.Id + ".", LoggingLevel.Normal, GetType());
            }

            return(callback.State);
        }
Beispiel #2
0
        /// <summary>
        /// Batches the <see cref="ScheduledCallback.NextExecution"/> value within the parameters of toleration (<see cref="ScheduledCallback.DelayToleranceBefore"/>
        /// and <see cref="ScheduledCallback.DelayToleranceAfter"/>), given the <see cref="ScheduledCallback"/>s that are already scheduled to run.
        /// </summary>
        /// <param name="callback">Callback.</param>
        private void BatchNextExecutionWithToleratedDelay(ScheduledCallback callback)
        {
            callback.Batched = false;

            // if delay tolerance is allowed, look for other scheduled callbacks in range of the delay tolerance.
            if (callback.DelayToleranceTotal.Ticks > 0)
            {
                DateTime rangeStart = callback.NextExecution.Value - callback.DelayToleranceBefore;
                DateTime rangeEnd   = callback.NextExecution.Value + callback.DelayToleranceAfter;

                ScheduledCallback closestCallbackInRange = _idCallback.Values.Where(existingCallback => existingCallback != callback &&         // the current callback will already have been added to the collection. don't consider it.
                                                                                    existingCallback.NextExecution.Value >= rangeStart &&       // consider callbacks within range of the current
                                                                                    existingCallback.NextExecution.Value <= rangeEnd &&         // consider callbacks within range of the current
                                                                                    !existingCallback.Batched &&                                // don't consider batching with other callbacks that are themselves batched, as this can potentially create batch cycling if the delay tolerance values are large.
                                                                                    existingCallback.State == ScheduledCallbackState.Scheduled) // consider callbacks that are already scheduled. we don't want to batch with callbacks that are, e.g., running or recently completed.

                                                                                                                                                // get existing callback with execution time closest to the current callback's time
                                                           .OrderBy(existingCallback => Math.Abs(callback.NextExecution.Value.Ticks - existingCallback.NextExecution.Value.Ticks))

                                                           // there might not be a callback within range
                                                           .FirstOrDefault();
                // use the closest if there is one in range
                if (closestCallbackInRange != null)
                {
                    SensusServiceHelper.Get().Logger.Log("Batching callback " + callback.Id + ":" + Environment.NewLine +
                                                         "\tCurrent time:  " + callback.NextExecution + Environment.NewLine +
                                                         "\tRange:  " + rangeStart + " -- " + rangeEnd + Environment.NewLine +
                                                         "\tNearest:  " + closestCallbackInRange.Id + Environment.NewLine +
                                                         "\tNew time:  " + closestCallbackInRange.NextExecution, LoggingLevel.Normal, GetType());

                    callback.NextExecution = closestCallbackInRange.NextExecution;
                    callback.Batched       = true;
                }
            }
        }
Beispiel #3
0
        public string ScheduleRepeatingCallback(ScheduledCallback callback, int initialDelayMS, int repeatDelayMS, bool repeatLag)
        {
            string callbackId = AddCallback(callback);

            ScheduleRepeatingCallback(callbackId, initialDelayMS, repeatDelayMS, repeatLag);
            return(callbackId);
        }
Beispiel #4
0
        public string ScheduleOneTimeCallback(ScheduledCallback callback, int delayMS)
        {
            string callbackId = AddCallback(callback);

            ScheduleOneTimeCallback(callbackId, delayMS);
            return(callbackId);
        }
Beispiel #5
0
        /// <summary>
        /// Unschedules the callback, first cancelling any executions that are currently running and then removing the callback from the scheduler.
        /// </summary>
        /// <param name="callback">Callback.</param>
        public async Task UnscheduleCallbackAsync(ScheduledCallback callback)
        {
            if (callback == null)
            {
                return;
            }

            SensusServiceHelper.Get().Logger.Log("Unscheduling callback " + callback.Id + ".", LoggingLevel.Normal, GetType());

            // interrupt any current executions
            CancelRaisedCallback(callback);

            // remove from the scheduler
            _idCallback.TryRemove(callback.Id, out ScheduledCallback removedCallback);

            CancelLocalInvocation(callback);

#if __IOS__
            await CancelRemoteInvocationAsync(callback);
#else
            await Task.CompletedTask;
#endif

            SensusServiceHelper.Get().Logger.Log("Unscheduled callback " + callback.Id + ".", LoggingLevel.Normal, GetType());
        }
Beispiel #6
0
 private string AddCallback(ScheduledCallback callback)
 {
     // treat the callback as if it were brand new, even if it might have been previously used (e.g., if it's being reschedueld). set a
     // new ID and cancellation token.
     callback.Id        = Guid.NewGuid().ToString();
     callback.Canceller = new CancellationTokenSource();
     _idCallback.TryAdd(callback.Id, callback);
     return(callback.Id);
 }
Beispiel #7
0
        /// <summary>
        /// Unschedules the callback.
        /// </summary>
        /// <returns>Task.</returns>
        /// <param name="id">Identifier.</param>
        public async Task UnscheduleCallbackAsync(string id)
        {
            ScheduledCallback callback = TryGetCallback(id);

            if (callback != null)
            {
                await UnscheduleCallbackAsync(callback);
            }
        }
Beispiel #8
0
        public bool ScheduleOneTimeCallback(ScheduledCallback callback, TimeSpan delay)
        {
            if (!_idCallback.TryAdd(callback.Id, callback))
            {
                return(false);
            }

            ScheduleOneTimeCallbackPlatformSpecific(callback.Id, delay);

            return(true);
        }
Beispiel #9
0
        public bool ScheduleRepeatingCallback(ScheduledCallback callback, TimeSpan initialDelay, TimeSpan repeatDelay, bool repeatLag)
        {
            if (!_idCallback.TryAdd(callback.Id, callback))
            {
                return(false);
            }

            ScheduleRepeatingCallbackPlatformSpecific(callback.Id, initialDelay, repeatDelay, repeatLag);

            return(true);
        }
Beispiel #10
0
        /// <summary>
        /// Cancels a callback that has been raised and is currently executing.
        /// </summary>
        /// <param name="callback">Callback.</param>
        public void CancelRaisedCallback(ScheduledCallback callback)
        {
            if (callback == null)
            {
                return;
            }

            callback.Canceller.Cancel();

            SensusServiceHelper.Get().Logger.Log("Cancelled callback " + callback.Id + ".", LoggingLevel.Normal, GetType());
        }
 public bool ContainsCallback(ScheduledCallback callback)
 {
     // we should never get a null callback id, but it seems that we are from android.
     if (callback?.Id == null)
     {
         SensusException.Report("Attempted to check scheduling status of callback with null id.");
         return(false);
     }
     else
     {
         return(_idCallback.ContainsKey(callback.Id));
     }
 }
        /// <summary>
        /// Unschedules the callback, first cancelling any executions that are currently running and then removing the callback from the scheduler.
        /// </summary>
        /// <param name="callback">Callback.</param>
        public void UnscheduleCallback(ScheduledCallback callback)
        {
            if (callback != null)
            {
                SensusServiceHelper.Get().Logger.Log("Unscheduling callback " + callback.Id + ".", LoggingLevel.Normal, GetType());

                // interrupt any current executions
                CancelRaisedCallback(callback);

                // remove from the scheduler
                ScheduledCallback removedCallback;
                _idCallback.TryRemove(callback.Id, out removedCallback);

                // tell the current platform cancel its hook into the system's callback system
                UnscheduleCallbackPlatformSpecific(callback);

                SensusServiceHelper.Get().Logger.Log("Unscheduled callback " + callback.Id + ".", LoggingLevel.Normal, GetType());
            }
        }
        public ScheduledCallbackState ScheduleCallback(ScheduledCallback callback)
        {
            if (callback.State != ScheduledCallbackState.Created)
            {
                SensusException.Report("Attemped to schedule callback " + callback.Id + ", which is in the " + callback.State + " state and not the " + ScheduledCallbackState.Created + " state.");
                callback.State = ScheduledCallbackState.Unknown;
            }
            else if (_idCallback.TryAdd(callback.Id, callback))
            {
                callback.NextExecution = DateTime.Now + callback.Delay;
                callback.State         = ScheduledCallbackState.Scheduled;

                ScheduleCallbackPlatformSpecific(callback);
            }
            else
            {
                SensusException.Report("Attempted to schedule duplicate callback for " + callback.Id + ".");
            }

            return(callback.State);
        }
Beispiel #14
0
        /// <summary>
        /// Requests remote invocation for a <see cref="ScheduledCallback"/>, to be delivered in parallel with the
        /// local invocation loop on iOS. Only one of these (the remote or local) will ultimately be allowed to
        /// run -- whichever arrives first.
        /// </summary>
        /// <returns>Task.</returns>
        /// <param name="callback">Callback.</param>
        private async Task RequestRemoteInvocationAsync(ScheduledCallback callback)
        {
            // not all callbacks are associated with a protocol (e.g., the app-level health test). because push notifications are
            // currently tied to the remote data store of the protocol, we don't currently provide PNR support for such callbacks.
            // on race conditions, it might be the case that the system attempts to schedule a duplicate callback. if this happens
            // the duplicate will not be assigned a next execution, and the system will try to unschedule/delete it. skip such
            // callbacks below.
            if (callback.Protocol != null && callback.NextExecution.HasValue)
            {
                try
                {
                    // the request id must differentiate the current device. furthermore, it needs to identify the
                    // request as one for a callback. lastly, it needs to identify the particular callback that it
                    // targets. the id does not include the callback invocation, as any newer requests for the
                    // callback should obsolete older requests.
                    string id = SensusServiceHelper.Get().DeviceId + "." + SENSUS_CALLBACK_KEY + "." + callback.Id;

                    PushNotificationUpdate update = new PushNotificationUpdate
                    {
                        Type    = PushNotificationUpdateType.Callback,
                        Content = JObject.Parse("{" +
                                                "\"callback-id\":" + JsonConvert.ToString(callback.Id) + "," +
                                                "\"invocation-id\":" + JsonConvert.ToString(callback.InvocationId) +
                                                "}")
                    };

                    PushNotificationRequest request = new PushNotificationRequest(id, SensusServiceHelper.Get().DeviceId, callback.Protocol, update, PushNotificationRequest.LocalFormat, callback.NextExecution.Value, callback.PushNotificationBackendKey);

                    await SensusContext.Current.Notifier.SendPushNotificationRequestAsync(request, CancellationToken.None);
                }
                catch (Exception ex)
                {
                    SensusException.Report("Exception while sending push notification request for scheduled callback:  " + ex.Message, ex);
                }
            }
        }
        /// <summary>
        /// Raises a callback.
        /// </summary>
        /// <returns>Async task</returns>
        /// <param name="callback">Callback to raise.</param>
        /// <param name="notifyUser">If set to <c>true</c>, then notify user that the callback is being raised.</param>
        /// <param name="scheduleRepeatCallback">Platform-specific action to execute to schedule the next execution of the callback.</param>
        /// <param name="letDeviceSleepCallback">Action to execute when the system should be allowed to sleep.</param>
        public virtual Task RaiseCallbackAsync(ScheduledCallback callback, bool notifyUser, Action scheduleRepeatCallback, Action letDeviceSleepCallback)
        {
            return(Task.Run(async() =>
            {
                try
                {
                    if (callback == null)
                    {
                        throw new NullReferenceException("Attemped to raise null callback.");
                    }

                    // the same callback cannot be run multiple times concurrently, so drop the current callback if it's already running. multiple
                    // callers might compete for the same callback, but only one will win the lock below and it will exclude all others until the
                    // the callback has finished executing.
                    bool runCallbackNow = false;
                    lock (callback)
                    {
                        if (callback.State == ScheduledCallbackState.Scheduled)
                        {
                            runCallbackNow = true;
                            callback.State = ScheduledCallbackState.Running;
                        }
                    }

                    if (runCallbackNow)
                    {
                        try
                        {
                            if (callback.Canceller.IsCancellationRequested)
                            {
                                throw new Exception("Callback " + callback.Id + " was cancelled before it was raised.");
                            }
                            else
                            {
                                SensusServiceHelper.Get().Logger.Log("Raising callback " + callback.Id + ".", LoggingLevel.Normal, GetType());

                                if (notifyUser)
                                {
                                    SensusContext.Current.Notifier.IssueNotificationAsync("Sensus", callback.UserNotificationMessage, callback.Id, callback.Protocol, true, callback.DisplayPage);
                                }

                                // if the callback specified a timeout, request cancellation at the specified time.
                                if (callback.CallbackTimeout.HasValue)
                                {
                                    callback.Canceller.CancelAfter(callback.CallbackTimeout.Value);
                                }

                                await callback.Action(callback.Id, callback.Canceller.Token, letDeviceSleepCallback);
                            }
                        }
                        catch (Exception raiseException)
                        {
                            SensusException.Report("Callback " + callback.Id + " threw an exception:  " + raiseException.Message, raiseException);
                        }
                        finally
                        {
                            // the cancellation token source for the current callback might have been canceled. if this is a repeating callback then we'll need a new
                            // cancellation token source because they cannot be reset and we're going to use the same scheduled callback again for the next repeat.
                            // if we enter the _idCallback lock before CancelRaisedCallback does, then the next raise will be cancelled. if CancelRaisedCallback enters the
                            // _idCallback lock first, then the cancellation token source will be overwritten here and the cancel will not have any effect on the next
                            // raise. the latter case is a reasonable outcome, since the purpose of CancelRaisedCallback is to terminate a callback that is currently in
                            // progress, and the current callback is no longer in progress. if the desired outcome is complete discontinuation of the repeating callback
                            // then UnscheduleRepeatingCallback should be used -- this method first cancels any raised callbacks and then removes the callback entirely.
                            try
                            {
                                if (callback.RepeatDelay.HasValue)
                                {
                                    callback.Canceller = new CancellationTokenSource();
                                }
                            }
                            catch (Exception ex)
                            {
                                SensusException.Report("Exception while assigning new callback canceller.", ex);
                            }
                            finally
                            {
                                callback.State = ScheduledCallbackState.Completed;

                                // schedule callback again if it is still scheduled with a valid repeat delay
                                if (ContainsCallback(callback) &&
                                    callback.RepeatDelay.HasValue &&
                                    callback.RepeatDelay.Value.Ticks > 0 &&
                                    scheduleRepeatCallback != null)
                                {
                                    // if this repeating callback is allowed to lag, schedule the next execution from the current time.
                                    if (callback.AllowRepeatLag.Value)
                                    {
                                        callback.NextExecution = DateTime.Now + callback.RepeatDelay.Value;
                                    }
                                    else
                                    {
                                        // otherwise, schedule the next execution from the time at which the current callback was supposed to be raised.
                                        callback.NextExecution = callback.NextExecution.Value + callback.RepeatDelay.Value;

                                        // if we've lagged so long that the next execution is already in the past, just reschedule for now. this will cause
                                        // the rescheduled callback to be raised as soon as possible, subject to delays in the systems scheduler (e.g., on
                                        // android most alarms do not come back immediately, even if requested).
                                        if (callback.NextExecution.Value < DateTime.Now)
                                        {
                                            callback.NextExecution = DateTime.Now;
                                        }
                                    }

                                    callback.State = ScheduledCallbackState.Scheduled;

                                    scheduleRepeatCallback();
                                }
                                else
                                {
                                    UnscheduleCallback(callback);
                                }
                            }
                        }
                    }
                    else
                    {
                        throw new Exception("Callback " + callback.Id + " was already running. Not running again.");
                    }
                }
                catch (Exception ex)
                {
                    SensusException.Report("Failed to raise callback:  " + ex.Message, ex);
                }
            }));
        }
Beispiel #16
0
        public virtual Task RaiseCallbackAsync(string callbackId, bool repeating, TimeSpan repeatDelay, bool repeatLag, bool notifyUser, Action <DateTime> scheduleRepeatCallback, Action letDeviceSleepCallback)
        {
            DateTime callbackStartTime = DateTime.Now;

            return(Task.Run(async() =>
            {
                try
                {
                    ScheduledCallback scheduledCallback = null;

                    // do we have callback information for the passed callbackId? we might not, in the case where the callback is canceled by the user and the system fires it subsequently.
                    if (!_idCallback.TryGetValue(callbackId, out scheduledCallback))
                    {
                        SensusServiceHelper.Get().Logger.Log("Callback " + callbackId + " is not valid. Unscheduling.", LoggingLevel.Normal, GetType());
                        UnscheduleCallback(callbackId);
                    }

                    if (scheduledCallback != null)
                    {
                        // the same callback action cannot be run multiple times concurrently. drop the current callback if it's already running. multiple
                        // callers might compete for the same callback, but only one will win the lock below and it will exclude all others until it has executed.
                        bool actionAlreadyRunning = true;
                        lock (scheduledCallback)
                        {
                            if (!scheduledCallback.Running)
                            {
                                actionAlreadyRunning = false;
                                scheduledCallback.Running = true;
                            }
                        }

                        if (actionAlreadyRunning)
                        {
                            SensusServiceHelper.Get().Logger.Log("Callback \"" + scheduledCallback.Id + "\" is already running. Not running again.", LoggingLevel.Normal, GetType());
                        }
                        else
                        {
                            try
                            {
                                if (scheduledCallback.Canceller.IsCancellationRequested)
                                {
                                    SensusServiceHelper.Get().Logger.Log("Callback \"" + scheduledCallback.Id + "\" was cancelled before it was raised.", LoggingLevel.Normal, GetType());
                                }
                                else
                                {
                                    SensusServiceHelper.Get().Logger.Log("Raising callback \"" + scheduledCallback.Id + "\".", LoggingLevel.Normal, GetType());

                                    if (notifyUser)
                                    {
                                        SensusContext.Current.Notifier.IssueNotificationAsync("Sensus", scheduledCallback.UserNotificationMessage, callbackId, scheduledCallback.ProtocolId, true, scheduledCallback.DisplayPage);
                                    }

                                    // if the callback specified a timeout, request cancellation at the specified time.
                                    if (scheduledCallback.CallbackTimeout.HasValue)
                                    {
                                        scheduledCallback.Canceller.CancelAfter(scheduledCallback.CallbackTimeout.Value);
                                    }

                                    await scheduledCallback.Action(callbackId, scheduledCallback.Canceller.Token, letDeviceSleepCallback);
                                }
                            }
                            catch (Exception ex)
                            {
                                string errorMessage = "Callback \"" + scheduledCallback.Id + "\" failed:  " + ex.Message;
                                SensusServiceHelper.Get().Logger.Log(errorMessage, LoggingLevel.Normal, GetType());
                                SensusException.Report(errorMessage, ex);
                            }
                            finally
                            {
                                // the cancellation token source for the current callback might have been canceled. if this is a repeating callback then we'll need a new
                                // cancellation token source because they cannot be reset and we're going to use the same scheduled callback again for the next repeat.
                                // if we enter the _idCallback lock before CancelRaisedCallback does, then the next raise will be cancelled. if CancelRaisedCallback enters the
                                // _idCallback lock first, then the cancellation token source will be overwritten here and the cancel will not have any effect on the next
                                // raise. the latter case is a reasonable outcome, since the purpose of CancelRaisedCallback is to terminate a callback that is currently in
                                // progress, and the current callback is no longer in progress. if the desired outcome is complete discontinuation of the repeating callback
                                // then UnscheduleRepeatingCallback should be used -- this method first cancels any raised callbacks and then removes the callback entirely.
                                try
                                {
                                    if (repeating)
                                    {
                                        lock (_idCallback)
                                        {
                                            scheduledCallback.Canceller = new CancellationTokenSource();
                                        }
                                    }
                                }
                                catch (Exception)
                                {
                                }
                                finally
                                {
                                    // if we marked the callback as running, ensure that we unmark it (note we're nested within two finally blocks so
                                    // this will always execute). this will allow others to run the callback.
                                    lock (scheduledCallback)
                                    {
                                        scheduledCallback.Running = false;
                                    }

                                    // schedule callback again if it was a repeating callback and is still scheduled with a valid repeat delay
                                    if (repeating && CallbackIsScheduled(callbackId) && repeatDelay.Ticks >= 0 && scheduleRepeatCallback != null)
                                    {
                                        DateTime nextCallbackTime;

                                        // if this repeating callback is allowed to lag, schedule the repeat from the current time.
                                        if (repeatLag)
                                        {
                                            nextCallbackTime = DateTime.Now + repeatDelay;
                                        }
                                        else
                                        {
                                            // otherwise, schedule the repeat from the time at which the current callback was raised.
                                            nextCallbackTime = callbackStartTime + repeatDelay;
                                        }

                                        scheduleRepeatCallback(nextCallbackTime);
                                    }
                                    else
                                    {
                                        UnscheduleCallback(callbackId);
                                    }
                                }
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    string errorMessage = "Failed to raise callback:  " + ex.Message;

                    SensusServiceHelper.Get().Logger.Log(errorMessage, LoggingLevel.Normal, GetType());

                    try
                    {
                        // TODO:  Report
                    }
                    catch (Exception)
                    {
                    }
                }
            }));
        }
 protected abstract void UnscheduleCallbackPlatformSpecific(ScheduledCallback callback);
Beispiel #18
0
        /// <summary>
        /// Raises a <see cref="ScheduledCallback"/>. This involves initiating the <see cref="ScheduledCallback"/>, setting up cancellation timing
        /// for the callback's action based on the <see cref="ScheduledCallback.Timeout"/>, and scheduling the next invocation of the
        /// <see cref="ScheduledCallback"/> in the case of repeating <see cref="ScheduledCallback"/>s. See <see cref="CancelRaisedCallback(ScheduledCallback)"/>
        /// for how to cancel a <see cref="ScheduledCallback"/> after it has been raised. Unlike other methods called via the app's entry points
        /// (e.g., push notifications, alarms, etc.), this method does not take a <see cref="CancellationToken"/>. The reason for this is that
        /// raising <see cref="ScheduledCallback"/>s is done from several locations, and cancellation is only needed due to background considerations
        /// on iOS. So we've centralized background-sensitive cancellation into the iOS override of this method.
        /// </summary>
        /// <returns>Async task</returns>
        /// <param name="callback">Callback to raise.</param>
        /// <param name="invocationId">Identifier of invocation.</param>
        public virtual async Task RaiseCallbackAsync(ScheduledCallback callback, string invocationId)
        {
            try
            {
                if (callback == null)
                {
                    throw new NullReferenceException("Attemped to raise null callback.");
                }

                if (SensusServiceHelper.Get() == null)
                {
                    throw new NullReferenceException("Attempted to raise callback with null service helper.");
                }

                // the same callback must not be run multiple times concurrently, so drop the current callback if it's already running. multiple
                // callers might compete for the same callback, but only one will win the lock below and it will exclude all others until the
                // the callback has finished executing. furthermore, the callback must not run multiple times in sequence (e.g., if the callback
                // is raised by the local scheduling system and then later by a remote push notification). this is handled by tracking invocation
                // identifiers, which are only runnable once.
                string initiationError = null;
                lock (callback)
                {
                    if (callback.State != ScheduledCallbackState.Scheduled)
                    {
                        initiationError += "Callback " + callback.Id + " is not scheduled. Current state:  " + callback.State;
                    }

                    if (invocationId != callback.InvocationId)
                    {
                        initiationError += (initiationError == null ? "" : ". ") + "Invocation ID provided for callback " + callback.Id + " does not match the one on record.";
                    }

                    if (initiationError == null)
                    {
                        callback.State = ScheduledCallbackState.Running;
                    }
                }

                if (initiationError == null)
                {
                    try
                    {
                        if (callback.Canceller.IsCancellationRequested)
                        {
                            SensusServiceHelper.Get().Logger.Log("Callback " + callback.Id + " was cancelled before it was raised.", LoggingLevel.Normal, GetType());
                        }
                        else
                        {
                            SensusServiceHelper.Get().Logger.Log("Raising callback " + callback.Id + ".", LoggingLevel.Normal, GetType());

#if __ANDROID__
                            // on android we wouldn't have yet notified the user using the callback's message. on ios, the
                            // message would have already been displayed to the user if the app was in the background. on
                            // ios we do not display callback messages if the app is foregrounded. see the notification
                            // delegate for how this is done.
                            await SensusContext.Current.Notifier.IssueNotificationAsync("Sensus", callback.UserNotificationMessage, callback.Id, true, callback.Protocol, null, callback.NotificationUserResponseAction, callback.NotificationUserResponseMessage);
#endif

                            // if the callback specified a timeout, request cancellation at the specified time.
                            if (callback.Timeout.HasValue)
                            {
                                callback.Canceller.CancelAfter(callback.Timeout.Value);
                            }

                            await callback.ActionAsync(callback.Canceller.Token);
                        }
                    }
                    catch (Exception raiseException)
                    {
                        SensusException.Report("Callback " + callback.Id + " threw an exception:  " + raiseException.Message, raiseException);
                    }
                    finally
                    {
                        // the cancellation token source for the current callback might have been canceled. if this is a repeating callback then we'll need a new
                        // cancellation token source because they cannot be reset and we're going to use the same scheduled callback again for the next repeat.
                        // if we enter the _idCallback lock before CancelRaisedCallback does, then the next raise will be cancelled. if CancelRaisedCallback enters the
                        // _idCallback lock first, then the cancellation token source will be overwritten here and the cancel will not have any effect on the next
                        // raise. the latter case is a reasonable outcome, since the purpose of CancelRaisedCallback is to terminate a callback that is currently in
                        // progress, and the current callback is no longer in progress. if the desired outcome is complete discontinuation of the repeating callback
                        // then UnscheduleRepeatingCallback should be used -- this method first cancels any raised callbacks and then removes the callback entirely.
                        try
                        {
                            if (callback.RepeatDelay.HasValue)
                            {
                                callback.Canceller = new CancellationTokenSource();
                            }
                        }
                        catch (Exception ex)
                        {
                            SensusException.Report("Exception while assigning new callback canceller.", ex);
                        }
                        finally
                        {
                            callback.State = ScheduledCallbackState.Completed;

                            // schedule callback again if it is still scheduled with a valid repeat delay
                            if (ContainsCallback(callback) &&
                                callback.RepeatDelay.HasValue &&
                                callback.RepeatDelay.Value.Ticks > 0)
                            {
                                callback.NextExecution = DateTime.Now + callback.RepeatDelay.Value;
                                callback.InvocationId  = Guid.NewGuid().ToString(); // set the new invocation ID before resetting the state so that concurrent callers won't run (their invocation IDs won't match)

                                BatchNextExecutionWithToleratedDelay(callback);

                                // the state needs to be updated after batching is performed, so that other callbacks don't
                                // attempt to batch with it, but before the invocations are requested, so that if the invocation
                                // comes back immediately (e.g., being scheduled in the past) the callback is scheduled and
                                // ready to run.
                                callback.State = ScheduledCallbackState.Scheduled;

                                await RequestLocalInvocationAsync(callback);

#if __IOS__
                                await RequestRemoteInvocationAsync(callback);
#endif
                            }
                            else
                            {
                                await UnscheduleCallbackAsync(callback);
                            }
                        }
                    }
                }
                else
                {
                    SensusServiceHelper.Get().Logger.Log("Initiation error for callback " + callback.Id + ":  " + initiationError, LoggingLevel.Normal, GetType());
                }
            }
            catch (Exception ex)
            {
                SensusException.Report("Exception raising callback:  " + ex.Message, ex);
            }
        }
Beispiel #19
0
 protected abstract void CancelLocalInvocation(ScheduledCallback callback);
Beispiel #20
0
 protected abstract Task RequestLocalInvocationAsync(ScheduledCallback callback);
Beispiel #21
0
 private async Task CancelRemoteInvocationAsync(ScheduledCallback callback)
 {
     await SensusContext.Current.Notifier.DeletePushNotificationRequestAsync(callback.PushNotificationBackendKey, callback.Protocol, CancellationToken.None);
 }