Reputation: 30737
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.
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
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
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