Click here to Skip to main content
11,410,536 members (57,256 online)
Click here to Skip to main content
Add your own
alternative version

A Simple Solution to Some Problems with Asynchrony in Silverlight

, 9 Mar 2010 CPOL
A small, extensible suite of classes that elegantly solve some common problems involving asynchrony and event handling that tend to occur when Silverlight and WCF are used together.
TaskManagerDemo.zip
TaskManagerDemo
Bin
Debug
Controls
Properties
Service References
ExampleServiceReference
configuration.svcinfo
configuration91.svcinfo
ExampleService.disco
ExampleService.wsdl
Reference.svcmap
TaskManagerDemo.ExampleServiceReference.CityZipCode.datasource
ServiceReferences.ClientConfig
Themes
Utility
TaskManagerDemo.Web
App_Data
bin
ClientBin
DataContracts
ExampleService.svc
Properties
TaskManagerDemo.Web.csproj.user
using System;
using System.Collections.Generic;

namespace TaskManagerDemo.Utility
{
    public enum TaskStatus
    {
        Unspecified = 0,
        NotStarted,
        InProgress,
        /// <summary>
        /// A Completed task may have completed successfully or with errors, or in some other nuanced state.
        /// Use the TaskManager class's Tag property to attach information indicating success / failure / nuances,
        /// error / warning information and the like.
        /// </summary>
        Completed
    }

    public class TaskManager
    {
        public TaskManager(string name)
        {
            Name = (!string.IsNullOrEmpty(name)) ? name : new Guid().ToString();
        }

        #region Methods

        /// <summary>
        /// Forces the status of an entire tree of TaskManager objects to be set to the specified value.
        /// This only works if the specified status is other than UnSpecified and the method is called on the root
        /// of a TaskManager tree.
        /// </summary>
        /// <param name="status">A TaskStatus value other than UnSpecified.</param>
        /// <returns>Whether or not the attempt to force the status of all nodes of the task tree to the specified
        /// value succeeded.</returns>
        public bool SetAllStatus(TaskStatus status)
        {
            return SetAllStatus(status, false);
        }

        /// <summary>
        /// Forces the status of an entire tree or subtree of TaskManager objects to be set to the specified value.
        /// This overload of the method permits the specified TaskManager object to be treated as the root of a tree,
        /// even if it is a child of another TaskManager object.
        /// </summary>
        /// <param name="status">A TaskStatus value other than UnSpecified.</param>
        /// <param name="treatAsRoot">If true, the specified TaskManager object is treated as the root of a TaskManager tree,
        /// permitting the Status properties of a subtree to be modified independently of ancestor objects in the tree.</param>
        /// <returns>Whether or not the attempt to force the status of all nodes of the task tree to the specified
        /// value succeeded.</returns>
        public bool SetAllStatus(TaskStatus status, bool treatAsRoot)
        {
            bool result = false;
            if (status != TaskStatus.Unspecified &&
                (treatAsRoot ||
                Parent == null))     // Is root. (RootParent == this).
            {
                foreach (TaskManager child in _childByName.Values)
                {
                    child.SetAllStatus(status, true);
                }
                if (_status != status)
                {
                    _status = status;
                    OnStatusChanged(new EventArgs());
                }
                result = true;
            }
            return result;
        }

        public bool SetAllStatus(TaskStatus status, Predicate<TaskManager> filterPredicate)
        {
            return SetAllStatus(status, false, filterPredicate);
        }

        public bool SetAllStatus(TaskStatus status, bool treatAsRoot, Predicate<TaskManager> filterPredicate)
        {
            bool result = false;
            if (status != TaskStatus.Unspecified &&
                (treatAsRoot ||
                Parent == null))     // Is root. (RootParent == this).
            {
                for (TaskManager node = this; node != null; node = TaskManager.WalkToNextFrom(node))
                {
                    if (filterPredicate == null || filterPredicate(node))
                    {
                        node.Status = TaskStatus.InProgress;
                    }
                }
            }
            return result;
        }

        public TaskStatus GetChildStatus(string childName)
        {
            TaskStatus result = TaskStatus.Unspecified;
            TaskManager child = FindChild(childName);
            if (child != null)
            {
                result = child.Status;
            }
            return result;
        }

        public TaskStatus GetDescendantStatus(string descendantName)
        {
            TaskStatus result = TaskStatus.Unspecified;
            TaskManager descendant = FindDescendant(descendantName);
            if (descendant != null)
            {
                result = descendant.Status;
            }
            return result;
        }

        /// <summary>
        /// Adds a child task to be managed.
        /// </summary>
        /// <param name="child">TaskManager object to add as a child task.</param>
        /// <exception cref="ArgumentException">The candidate child is already the child of another TaskManager object,
        /// or a TaskManager object with the candidate child's name (which may, but need not, be the same object)
        /// already exists in the tree of TaskManager objects.</exception>
        public void AddChild(TaskManager child)
        {
            if (child.Parent != null)
            {
                throw new ArgumentException("The candidate child is already the child of another TaskManager object.");
            }

            if (RootParent.FindDescendant(child.Name) != null)
            {
                throw new ArgumentException("A TaskManager object with the candidate child's name already exists in the tree of TaskManager objects.");
            }

            _childByName.Add(child.Name, child);
            child.Parent = this;
            OnChildAdded(new TaskManagerChildEventArgs { Child = child });
        }

        public void AddChildren(TaskManager[] childArray)
        {
            foreach (TaskManager child in childArray)
            {
                AddChild(child);
            }
        }

        public virtual void AddChild(string name)
        {
            AddChild(new TaskManager(name));
        }

        public bool RemoveChild(TaskManager child)
        {
            bool result = _childByName.Remove(child.Name);
            if (result)
            {
                child.Parent = null;
                OnChildRemoved(new TaskManagerChildEventArgs { Child = child });
            }
            return result;
        }

        public bool RemoveChild(string name)
        {
            bool result = false;
            TaskManager child = FindChild(name);
            if (child != null)
            {
                result = RemoveChild(child);
            }
            return result;
        }

        //public bool RemoveAllChildren() { return false; }   // For future implementation?

        public TaskManager FindChild(string name)
        {
            TaskManager result;
            _childByName.TryGetValue(name, out result);
            return result;
        }

        public TaskManager FindDescendant(string name)
        {
            TaskManager result = FindChild(name);
            if (result == null)
            {
                foreach (TaskManager child in _childByName.Values)
                {
                    result = child.FindDescendant(name);
                    break;
                }
            }
            return result;
        }

        public TaskManager FindFirstChild()
        {
            return FindNextChild(null);
        }

        public TaskManager FindNextChild(TaskManager currentChild)
        {
            TaskManager result = null;
            List<TaskManager> siblingsList = this.ChildList;
            if (siblingsList.Count > 0)
            {
                if (currentChild == null)
                {
                    result = siblingsList[0];
                }
                else
                {
                    for (int index = 0; index < siblingsList.Count; ++index)
                    {
                        if (siblingsList[index] == currentChild)
                        {
                            int nextIndex = index + 1;
                            if (nextIndex < siblingsList.Count)
                            {
                                result = siblingsList[nextIndex];
                            }
                        }
                    }
                }
            }
            return result;
        }

        /// <summary>
        /// Traverses a tree of TaskManager objects, one step at a time. The traversal is pre-order in terms of the
        /// (unspecified but consistent) order of the list of child tasks (ChildList). Thus, the order in which the
        /// nodes are visited is indeterminate but it is easy to implement a tree walk in which each node is visited
        /// exactly once.
        /// </summary>
        /// <param name="currentTask">Task from which to find the next unvisited task.</param>
        /// <returns>The next task, relative to the specified currentTask, in a traversal of the tree of TaskManager
        /// objects to which currentTask belongs. Returns null when the tree walk is complete.</returns>
        static public TaskManager WalkToNextFrom(TaskManager currentTask)
        {
            TaskManager result = null;
            if (currentTask != null)
            {
                // Try to find leftmost child of currentTask as next task.
                result = currentTask.FindFirstChild();
                if (result == null)
                {
                    // Try to find sibling "to the right of" currentTask as the next task.
                    // If that fails, move up to the parent and try the same, etc.
                    TaskManager thisTask = currentTask;
                    for (; ; )
                    {
                        TaskManager parent = thisTask.Parent;
                        if (parent == null)
                        {
                            break;
                        }
                        result = parent.FindNextChild(thisTask);
                        if (result != null)
                        {
                            break;
                        }
                        thisTask = parent;
                    }
                }
            }
            return result;
        }

        /// <summary>
        /// Executes the specified action on each member of the sub-tree rooted in the current node.
        /// </summary>
        /// <param name="action">Specifies an action to be performed.</param>
        public void ForEach(Action<TaskManager> action)
        {
            for (TaskManager node = this; node != this.Parent; node = node = WalkToNextFrom(node))
            {
                action(node);
            }
        }

        #endregion Methods
        #region Properties

        /// <summary>
        /// The Task Manager instance's unique identifier within a tree of TaskManager objects to which it belongs.
        /// </summary>
        public string Name { get; private set; }

        /// <summary>
        /// If true, the task's status will be automatically set to Completed when the last of its child tasks
        /// has its status set to Completed.
        /// </summary>
        public bool AutoComplete { get; set; }

        /// <summary>
        /// The status of the task managed by the TaskManager instance.
        /// </summary>
        protected TaskStatus _status;
        public TaskStatus Status
        {
            get
            {
                return _status;
            }
            set
            {
                bool requestCanBeHonored = false;
                switch (value)
                {
                    //case TaskStatus.Unspecified:
                    default:
                        break;
                    case TaskStatus.NotStarted:
                        requestCanBeHonored = CanBeSetNotStarted;
                        break;
                    case TaskStatus.InProgress:
                        requestCanBeHonored = CanBeSetInProgress;
                        break;
                    case TaskStatus.Completed:
                        requestCanBeHonored = CanBeSetCompleted;
                        break;
                }
                if (requestCanBeHonored)
                {
                    if (_status != value)
                    {
                        _status = value;
                        OnStatusChanged(new EventArgs());
                    }
                    if (value == TaskStatus.Completed && Parent != null && Parent.AutoComplete)
                    {
                        // The parent task will be completed if this is the last of its child tasks to be completed.
                        Parent.Status = TaskStatus.Completed;
                    }
                }
            }
        }

        public TaskManager Parent { get; private set; }

        public TaskManager RootParent
        {
            get
            {
                return (Parent != null) ? Parent.RootParent : this;
            }
        }

        protected Dictionary<string, TaskManager> _childByName = new Dictionary<string, TaskManager>();
        /// <summary>
        /// Gets a list of the TaskManager object's child tasks, if any. Implemented using the Keys collection of an
        /// internal Dictionary, rendering the order of the child tasks indeterminate but consistent. Marked virtual
        /// so that derived classes may implement override implementations that provide specific ordering. Such
        /// an override will also affect the behavior FindFirstChild, FindNextChild and WalkToNextFrom.
        /// </summary>
        public virtual List<TaskManager> ChildList
        {
            get
            {
                return new List<TaskManager>(_childByName.Values);
            }
        }

        /// <summary>
        /// Gets the result of applying the constraints on setting a task's status to NotStarted.
        /// Does not apply to SetAllStatus.
        /// </summary>
        public virtual bool CanBeSetNotStarted
        {
            get
            {
                bool result = false;
                switch (Status)
                {
                    //case TaskStatus.Unspecified:
                    //case TaskStatus.NotStarted:
                    default:
                        result = true;
                        break;
                    case TaskStatus.InProgress:
                        //result = false;
                        break;
                    case TaskStatus.Completed:
                        result = (Parent == null || Parent.CanBeSetNotStarted);
                        break;
                }
                return result;
            }
        }

        /// <summary>
        /// Gets the result of applying the constraints on setting a task's status to InProgress.
        /// Does not apply to SetAllStatus.
        /// </summary>
        public virtual bool CanBeSetInProgress
        {
            get
            {
                // The logically permitted transition is from NotStarted to InProgress. It is also possible to transition
                // from UnSpecified or Completed to NotStarted and thus indirectly to InProgress. However, it doesn't make
                // sense to start a sub-task when its parent task is not in progress.
                return (Parent == null || Parent.Status == TaskStatus.InProgress);
            }
        }

        /// <summary>
        /// Gets the result of applying the constraints on setting a task's status to Completed.
        /// Does not apply to SetAllStatus.
        /// </summary>
        public virtual bool CanBeSetCompleted
        {
            get
            {
                bool result = false;
                switch (Status)
                {
                    //case TaskStatus.Unspecified:
                    //case TaskStatus.NotStarted:
                    default:
                        break;
                    case TaskStatus.InProgress:
                        result = true;
                        foreach (TaskManager tsm in ChildList)
                        {
                            if (tsm.Status != TaskStatus.Completed)
                            {
                                result = false;
                                break;
                            }
                        }
                        break;
                    case TaskStatus.Completed:
                        result = true;
                        break;
                }
                return result;
            }
        }

        /// <summary>
        /// Optional information appended to assist in tracking a task (e.g. extended task status info,
        /// such as error details).
        /// </summary>
        public object Tag { get; set; }

        #endregion Properties
        #region Events

        public event EventHandler<EventArgs> StatusChanged;
        public event EventHandler<TaskManagerChildEventArgs> ChildAdded;
        public event EventHandler<TaskManagerChildEventArgs> ChildRemoved;

        protected void OnStatusChanged(EventArgs e)
        {
            if (StatusChanged != null)
            {
                StatusChanged(this, e);
            }
        }

        protected void OnChildAdded(TaskManagerChildEventArgs e)
        {
            if (ChildAdded != null)
            {
                ChildAdded(this, e);
            }
        }

        protected void OnChildRemoved(TaskManagerChildEventArgs e)
        {
            if (ChildRemoved != null)
            {
                ChildRemoved(this, e);
            }
        }

        #endregion Events
    }

    public class TaskManagerChildEventArgs : EventArgs
    {
        public TaskManager Child { get; set; }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

George Henry 1954
Software Developer (Senior) Concur
United States United States
George Henry has worked as a software developer for more than 20 years. He is currently employed by Concur in Bellevue, Washington, USA.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.150414.5 | Last Updated 9 Mar 2010
Article Copyright 2009 by George Henry 1954
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid