为什么我的System.Threading.Task.Continue会在错误的时间触发

时间:2016-07-14 15:08:43

标签: c# multithreading

我正在尝试处理System.Threading.Tasks.Task

中的异常

我之前没有使用过这些,似乎误解了ContinueWith的工作方式;因此我的ContinueWith在错误的时间开火。

鉴于以下内容; workers只是我长期运行流程的列表。

......
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();
    })
.....

我的处理程序方法是

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

我的TestWorker是:

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

并且

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()只是设置运行DoWork的时间间隔

会发生什么......

调试时,会创建并启动所有任务。一旦DoWork第一次调用ScheduleTimer(),我的ExceptionHandler就会被击中;如截图所示 - 这对两个工人都有效。

enter image description here

当在TestWorker2中遇到异常时,调试器将不会从那里继续 - 因为我按下继续,希望点击我的ExceptionHandler,调试器只是继续抛出异常。

我希望实现的目标

我希望我的ExceptionHandler仅在抛出正在运行的任务中的异常时触发。我发现我进入ExceptionHandler的唯一时间就是它运行的时间,而我的实际异常只会保持循环。

我缺少什么?

根据评论,这是主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);
        }
    }

2 个答案:

答案 0 :(得分:4)

从屏幕截图中可以看出arg2是您的TaskContinuationOptions.OnlyOnFaulted对象,这是出错的最大线索。由于您传递了Action<Task, Object>,因此使用ContinueWith的{​​{3}}重载,这会导致您的延续选项作为state参数传入。

ExceptionHandler更改为

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

因此您将使用Task.ContinueWith Method (Action<Task, Object>, Object)重载,或者您可以将通话更改为

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

这样您就可以开始使用Task.ContinueWith(Action<Task>, TaskContinuationOptions)重载。

答案 1 :(得分:0)

您的日志记录组件可能不支持多个并发写入。 如果您有可能,我建议您将代码重构为async / await模式,它将更具可读性。 假设您创建了要运行的所有任务的列表:

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

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

然后在await try块所包围的列表中使用catch

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

另外,请确保您在Thread.Wait(xxx)内没有进行任何Thread.XXX次调用(或任何其他ScheduleTimer()调用),因为任务和线程不能很好地协同工作。

希望它有所帮助!