Beispiel #1
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)
                    {
                    }
                }
            }));
        }
        /// <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);
                }
            }));
        }