Darren Wainwright
Darren Wainwright

Reputation: 30737

Why is my System.Threading.Task.ContinueWith firing at the wrong time

I am trying to handle exceptions from a System.Threading.Tasks.Task

I haven't used these before, and seem to be misunderstanding how the ContinueWith works; thus my ContinueWith is firing at the wrong time.

Given the following; workers is just a list of my long running processes.

......
workers.Add(new Workers.Tests.TestWorker1());
workers.Add(new Workers.Tests.TestWorker2());

// Start all the workers.
workers.ForEach(worker =>
    {
    // worker.Start schedules a timer and calls DoWork in the worker
    System.Threading.Tasks.Task task = new System.Threading.Tasks.Task(worker.Start); 
    task.ContinueWith(ExceptionHandler, TaskContinuationOptions.OnlyOnFaulted);
    task.Start();
    })
.....

My handler method is

private void ExceptionHandler(System.Threading.Tasks.Task arg1, object arg2)
{
    DebugLogger.Write("uh oh.. it died");
}

My TestWorker's are:

class TestWorker1 : Worker
    {
        int count = 1;
        public override void DoWork(object timerState)
        {
            DebugLogger.Write(string.Format("{0} ran {1} times", workerName, count));
            count++;
            ScheduleTimer();
        }
    }

And

class TestWorker2 : Worker
{
    int count = 1;
    public override void DoWork(object timerState)
    {
        DebugLogger.Write(string.Format("{0} ran {1} times", workerName, count));
        count++;

        if (count == 3)
            throw new Exception("I'm going to die....");

        ScheduleTimer();
    }
}

ScheduleTimer() simply sets an interval for DoWork to be run

What happens...

When I debug, all tasks are created and started. As soon as theDoWork has called ScheduleTimer() for the first time, my ExceptionHandler is hit; as shown in this screenshot - this happens for both workers.

enter image description here

When the exception is hit in TestWorker2 the debugger will not move on from there - in that i press continue, hoping to hit my ExceptionHandler, and the debugger just keeps throwing the exception.

What I am hoping to achieve

I would like my ExceptionHandler to only fire when an exception within the running tasks is thrown. I'm finding the only time i get into my ExceptionHandler is when it's run, and my actual exception just keeps looping.

What am i missing?

Per comment, here is the code for the main Worker

public abstract class Worker : IDisposable
    {
        internal string workerName;
        internal Timer scheduler;
        internal DateTime scheduledTime;

        public Worker()
        {
            string t = this.GetType().ToString();
            workerName = t.Substring(t.LastIndexOf(".") + 1).AddSpacesBeforeUppercase(true).Trim();
        }

        /// <summary>
        /// Set to true when the worker is performing its task, false when its complete
        /// </summary>
        public bool IsCurrentlyProcessing { get; set; }

        public void Start()
        {
            DebugLogger.Write(workerName + "  Started");
            ScheduleTimer();
        }

        /// <summary>
        /// default functionality for setting up the timer. 
        /// Typically, the timer will fire in 60  second intervals
        /// Override this method in child classes for different functionality
        /// </summary>
        public virtual void ScheduleTimer()
        {
            scheduler = new Timer(new TimerCallback(DoWork));
            int interval = 60;
            int.TryParse(ConfigurationManager.AppSettings[string.Format("{0}{1}", workerName.Replace(" ", ""), "Interval")], out interval);
            scheduledTime = DateTime.Now.AddSeconds(interval);
            if (DateTime.Now > scheduledTime)
                scheduledTime = scheduledTime.AddSeconds(interval);

            int dueTime = Convert.ToInt32(scheduledTime.Subtract(DateTime.Now).TotalMilliseconds);
            scheduler.Change(dueTime, Timeout.Infinite);
        }

        public abstract void DoWork(object timerState);

        public void Stop()
        {
            // kill stuff
            if (scheduler != null)
                scheduler.Dispose();
            DebugLogger.Write(workerName + " stop");
            this.Dispose();
        }

        private bool disposed = false;
        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
                if (disposing)
                {
                    // any specific cleanup

                }
            this.disposed = true;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    }

Upvotes: 1

Views: 139

Answers (2)

Scott Chamberlain
Scott Chamberlain

Reputation: 127583

From your screenshot it appears that arg2 is your TaskContinuationOptions.OnlyOnFaulted object, that is the biggest clue of what is going wrong. Because you passed in a Action<Task, Object> it is using the Task.ContinueWith Method (Action<Task, Object>, Object) overload of ContinueWith, this is causing your continuation options to be passed in as the state parameter.

Either change ExceptionHandler to

private void ExceptionHandler(System.Threading.Tasks.Task arg1)
{
    DebugLogger.Write("uh oh.. it died");
}

so you will use the Task.ContinueWith(Action<Task>, TaskContinuationOptions) overload, or you can change your call to

task.ContinueWith(ExceptionHandler, null, TaskContinuationOptions.OnlyOnFaulted);

so that you will start using the Task.ContinueWith(Action<Task, Object>, Object, TaskContinuationOptions) overload.

Upvotes: 4

Itay Podhajcer
Itay Podhajcer

Reputation: 2654

Might be caused by your logging component not supporting multiple concurrent writes. If it's possible for you, I'd suggest you refactor the code to the async/await pattern, it will be much more readable. Let's say you create a list of all the tasks you want to run:

List<Task> tasks = new List<Task>();

workers.ForEach(worker => tasks.Add(Task.Run(() => worker.Start())));

and then use await on the list surrounded by a try catch block:

try
{
    await Task.WhenAll(tasks);
}
catch (Exception ex)
{
    DebugLogger.Write("uh oh.. it died");
}

Also, make sure you are not doing any Thread.Wait(xxx) calls (or any other Thread.XXX calls for that matter) inside ScheduleTimer(), because tasks and threads don't play nice together.

Hope it helps!

Upvotes: 0

Related Questions