/// <summary> /// Method used as a thread starting point for tasks /// </summary> private void TaskThread(TaskEntry entry) { LOG("TaskThread('" + entry + "') - Start"); ASSERT(entry != null, "Missing parameter 'entry'"); System.Exception callbackException = null; try { // Copy CultureInfos to this thread if (entry.CreatorCultureInfo != null) { Thread.CurrentThread.CurrentCulture = entry.CreatorCultureInfo; } if (entry.CreatorUICultureInfo != null) { Thread.CurrentThread.CurrentUICulture = entry.CreatorUICultureInfo; } entry.Callback(entry); } catch (System.Exception ex) { callbackException = ex; } // The task's thread is terminated. Remove it from this queue. // No need to Abort() it => /*abortRunningTask=*/false // but 'launchChecksAtEnd=true' will launch CheckRunningTasks() after removing it from 'RunningTasks' (if its status is 'Running' which it still should be...) LOG("TaskThread('" + entry + "') - Removing TaskEntry"); entry.Remove(); // NB: If the task has already been manually Remove()d, this won't have any effect // entry.Status should now be 'Removed' if (callbackException != null) { // The task threw an exception. Notify it LOG("TaskThread('" + entry + "') - The task threw an exception: " + callbackException); entry.SetCallbackException(callbackException); } LOG("TaskThread('" + entry + "') - Calling entry.TriggerOnTerminated()"); entry.Terminate(); LOG("TaskThread('" + entry + "') - Exit"); }
/// <summary> /// The one and only method to add a task to this queue. /// </summary> private TaskEntry CreateTask(DateTime executionDate, Action <TaskEntry> callback) { LOG("CreateTask('" + executionDate + "') - Start"); ASSERT(executionDate.Kind == DateTimeKind.Utc, "CreateTimer() called with a non-UTC DateTime"); TaskEntry entry; lock ( LockObject ) { LOG("CreateTask('" + executionDate + "') - Lock acquired"); if (Disposed) { // Cannot create tasks anymore throw new ApplicationException("This TasksQueue instance is disposed"); } entry = new TaskEntry(this, executionDate, TaskEntry.Statuses.Delayed, callback); CheckValidity(); List <Guid> list; if (!DelayedTasks.TryGetValue(entry.ExecutionDate, out list)) { list = new List <Guid>(); DelayedTasks.Add(entry.ExecutionDate, list); } list.Add(entry.ID); AllTasks.Add(entry.ID, entry); CheckValidity(); } CheckDelayedTasks(); LOG("CreateTask('" + executionDate + "') - Exit"); return(entry); }
/// <summary> /// The one and only method that can be used to remove a task from this queue. /// </summary> /// <remarks>Can only be called from the TastEntry itself</remarks> internal void Remove(TaskEntry entry) { LOG("Remove('" + entry + ") - Start"); ASSERT(entry != null, "Missing parameter 'entry'"); bool launchChecksAtEnd; bool checkDelayedTasks = false; bool checkRunningTasks = false; lock ( LockObject ) { LOG("Remove('" + entry + ") - Lock acquired"); CheckValidity(); launchChecksAtEnd = Disposed ? false : true; entry.GetStatus((status) => { var id = entry.ID; switch (status) { case TaskEntry.Statuses.Removed: // Already removed // ASSERT( !AllTasks.ContainsKey(id), "The task is declared as Removed but is still in AllTasks" ); <= This can happen when 2 threads are removing the same TaskEntry at the same time => log but don't assert LOG("Remove('" + entry + ") - Is already removed"); return; case TaskEntry.Statuses.Delayed: { // Remove item from 'DelayedTasks' LOG("Remove('" + entry + ") - Is delayed"); var executionDate = entry.ExecutionDate; var list = DelayedTasks[executionDate]; var rc = list.Remove(id); ASSERT(rc, "Task was not in 'DelayedTasks'"); if (list.Count == 0) { // No more task for this DateTime LOG("Remove('" + entry + ") - No more task for " + executionDate); DelayedTasks.Remove(executionDate); } // The list has changed checkDelayedTasks = true; break; } case TaskEntry.Statuses.Queued: { LOG("Remove('" + entry + ") - Is Queued"); // Recreate 'QueuedTasks' without this task's ID QueuedTasks = new Queue <Guid>(QueuedTasks.Where((itemId) => (itemId != id))); // The list has changed checkRunningTasks = true; break; } case TaskEntry.Statuses.Running: { LOG("Remove('" + entry + ") - Is Running"); var rc = RunningTasks.Remove(id); ASSERT(rc, "Task was not in 'RunningTasks'"); checkRunningTasks = true; break; } default: throw new NotImplementedException("Unknown task status '" + status + "'"); } { // Remove the task from 'AllTasks' and set its status to 'Removed' LOG("Remove('" + entry + ") - Removing from AllTasks"); var rc = AllTasks.Remove(id); ASSERT(rc, "Task was not in 'AllTasks'"); entry.SetStatus(TaskEntry.Statuses.Removed); } }); CheckValidity(); } LOG("Remove('" + entry + ") - Lock released"); if (launchChecksAtEnd) { if (checkDelayedTasks) { // The 'DelayedTasks' has changed CheckDelayedTasks(); } if (checkRunningTasks) { // The 'RunningTasks' has changed CheckRunningTasks(); } LOG("Remove('" + entry + ") - Invoking " + onEntryRemoved.Count + " callbacks for 'OnEntryRemoved' event"); onEntryRemoved.Invoke(entry); } LOG("Remove('" + entry + ") - Exit"); }
public void ForEach <T>(T[] arguments, Action <TaskEntry, T> action) { ASSERT(arguments != null, "Missing parameter 'arguments'"); ASSERT(action != null, "Missing parameter 'action'"); // Create arguments.Length TaskEntries int count = arguments.Length; var resetEvent = new ManualResetEvent(false); var entries = new TaskEntry[count]; var exceptionContainer = new VolatileContainer <Exception> { Value = null }; for (int i = 0; i < arguments.Length; ++i) { var argument = arguments[i]; entries[i] = CreateTask((entry) => { try { action(entry, argument); } catch (System.Exception ex) { lock ( exceptionContainer ) { // If this is the first exception we catch ... if (exceptionContainer.Value == null) { // ... save it exceptionContainer.Value = ex; } } // Release the main thread now so it can 'Remove()' all remaining TaskEntries resetEvent.Set(); } finally { var newCount = Interlocked.Decrement(ref count); if (newCount == 0) { // The last TaskEntry has finished resetEvent.Set(); } } }); } // Wait for all TaskEntries to exit resetEvent.WaitOne(new TimeSpan(0, 0, 30)); // if the resetEvent exited because of an exception ... if (exceptionContainer.Value != null) { // ... discard all remaining TaskEntries (it is the responsibility of the caller to check 'entry.IsRemoved()') ... foreach (var entry in entries) { entry.Remove(); } // ... and rethrow the caught exception throw exceptionContainer.Value; } }
public void Dispose() { if( Disposed ) // Already disposed return; Disposed = true; if( Connection != null ) { try { Connection.SendResponseMessage(RootMessage.CreateResetRootMessage()); } catch( System.Exception ex ) { ConnectionList.FatalExceptionHandler( "Call to Connection.SendReset() threw an exception", ex ); } } if( DisconnectionTimeout != null ) { try { DisconnectionTimeout.Remove(); } catch( System.Exception ex ) { ConnectionList.FatalExceptionHandler( "Call to DisconnectionTimeout.Remove() threw an exception", ex ); } DisconnectionTimeout = null; } if( StaleTimeout != null ) { try { StaleTimeout.Remove(); } catch( System.Exception ex ) { ConnectionList.FatalExceptionHandler( "Call to StaleTimeout.Remove() threw an exception", ex ); } StaleTimeout = null; } }
private void HandleMessageThread(MessageContext messageContext, TaskEntry taskEntry) { ASSERT( messageContext != null, "Missing parameter 'messageContext'" ); // ASSERT( taskEntry != null, "Missing parameter 'taskEntry'" ); => 'null' means "CallbackItem.CallbackThreaded == null" var message = messageContext.Message; var callbackItem = messageContext.CallbackItem; LOG( "HandleMessageThread(" + taskEntry + "," + message + ") - Start" ); bool contextRestored = false; try { if(! callbackItem.IsThreaded ) { // Inline message handler => This thread is still the HTTP handler's thread => No need to restore thread's context ASSERT( callbackItem.CallbackDirect != null, "If not 'IsThreaded' then 'callbackItem.CallbackDirect' is supposed to be set" ); ASSERT( callbackItem.CallbackThreaded == null, "If not 'IsThreaded' then 'callbackItem.CallbackThreaded' is supposed to be null" ); callbackItem.CallbackDirect( message ); } else // Threaded message handler => Must restore some of the HTTP handler's thread context { ASSERT( callbackItem.CallbackDirect == null, "If 'IsThreaded' then 'callbackItem.CallbackDirect' is supposed to be null" ); ASSERT( callbackItem.CallbackThreaded != null, "If 'IsThreaded' then 'callbackItem.CallbackThreaded' is supposed to be set" ); // Restore message's context messageContext.RestoreContext(); contextRestored = true; callbackItem.CallbackThreaded( taskEntry, message ); } LOG( "HandleMessageThread(" + taskEntry + "," + message + ") - Exit" ); } catch( System.Reflection.TargetInvocationException ex ) { LOG( "HandleMessageThread(" + taskEntry + "," + message + ") - TargetInvocationException" ); SendMessageToConnection( message.SenderConnectionID, Message.CreateExceptionMessage(exception:ex.InnerException, sourceMessage:message) ); } catch( System.Exception ex ) { LOG( "HandleMessageThread(" + taskEntry + "," + message + ") - Exception" ); SendMessageToConnection( message.SenderConnectionID, Message.CreateExceptionMessage(exception:ex, sourceMessage:message) ); } finally { if( contextRestored && (ClearMessageContextObject != null) ) // Clear the restored message context try { ClearMessageContextObject(); } catch( System.Exception ex ) { FAIL( "'ClearMessageContextObject()' threw an exception (" + ex.GetType().FullName + "): " + ex.Message ); } } }