繁体   English   中英

我将如何监视和重新启动在 C# 中抛出异常的任务?

[英]How would I monitor and restart tasks that throw exceptions in C#?

假设我有一个程序可以实例化三个无限期运行的任务。 这些任务旨在并行运行。 但是,假设已知这些任务偶尔会因网络错误而抛出异常。

监视任务并在必要时重新启动它的最简单技术是什么?

我解决这个问题的方法是监视 Task.Status 数据,如果任务出错,只需调用 Task.Start() 方法。

但是,此代码不起作用,因为任务异常会导致整个应用程序崩溃。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            var my_task = Program.MainAsync();

            my_task.Wait();
        }


        public static async Task MainAsync()
        {
            var task_1 = Program.TaskMethod("1");
            var task_2 = Program.TaskMethod("2");
            var task_3 = Program.TaskMethod("3");

            // loop indefinitely restarting task if necessary
            while(true)
            {
                if (task_1.Status == TaskStatus.Faulted)
                    task_1.Start();
                if (task_2.Status == TaskStatus.Faulted)
                    task_2.Start();
                if (task_3.Status == TaskStatus.Faulted)
                    task_3.Start();

                await Task.Delay(1000);

            }

        }

        public static async Task TaskMethod(string task_id)
        {
            Console.WriteLine("Starting Task {0}", task_id);
            while(true)
            {
                await Task.Delay(5000);
                Console.WriteLine("Hello from task {0}", task_id);

                int i = 0;
                int b = 32 / i;

            }
        }

    }
}

由于Task无法重新启动,因此您必须使用任务工厂Func<Task>每次Task失败时都可以反复调用,以创建更多任务。

var taskFactories = new List<Func<Task>>();

taskFactories.Add(() => TaskMethod("1"));
taskFactories.Add(() => TaskMethod("2"));
taskFactories.Add(() => TaskMethod("3"));

var runningTasks = taskFactories.ToDictionary(factory => factory());

while (runningTasks.Count > 0)
{
    // Wait for something to happen, good or bad
    var completedTask = await Task.WhenAny(runningTasks.Keys);
    if (completedTask.IsFaulted) // Something bad happened
    {
        var factory = runningTasks[completedTask];
        var newTask = factory();
        runningTasks.Remove(completedTask);
        runningTasks.Add(newTask, factory);
    }
    else // A task just finished normally or was canceled
    {
        runningTasks.Remove(completedTask);
    }
}

如果线程出现异常,我也会崩溃。 所以我学会了总是将 try/catch 添加到任何线程或任务方法。 此外,异常消息可以帮助调试/改进程序。

我只想:

  • 在任务方法中使用 try catch
  • 使用异常作为任务的返回值
  • 使用 WaitAny 不要太频繁地轮询,只对失败/任务结束做出反应
  • 在这里使用 SortedDictionary 来识别任务及其开始参数

这是代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            var my_task = Program.MainAsync();

            my_task.Wait();
        }


        public static async Task MainAsync()
        {
            var tasks = new SortedDictionary<string, Task<Exception>>();
            for (int idx = 1; idx <= 3; idx++)
            {
                var parameter = idx.ToString();
                tasks.Add(parameter, Program.TaskMethod(parameter));
            }

            // loop indefinitely restarting task if necessary
            while (tasks.Any())
            {
                var taskArr = tasks.Values.ToArray();
                var finishedIdx = Task.WaitAny(taskArr, 30000);

                if (0 <= finishedIdx)
                {
                    var finishedTask = taskArr[finishedIdx];
                    var parameter = tasks.First(kvp => kvp.Value == finishedTask).Key;
                    if (finishedTask.Result != null) // exception was thrown
                    {
                        tasks[parameter] = Program.TaskMethod(parameter); // restart the task!
                    }
                    else
                    {
                        tasks.Remove(parameter);
                    }
                }
            }

        }

        public static async Task<Exception> TaskMethod(string task_id)
        {
            try
            {
                Console.WriteLine("Starting Task {0}", task_id);

                while (true)
                {
                    await Task.Delay(5000);
                    Console.WriteLine("Hello from task {0}", task_id);

                    int i = 0;
                    int b = 32 / i;

                }

                return null;
            }
            catch (Exception exc)
            {
                return exc;
            }
        }

    }
}

然后输出是这样的:

Starting Task 1
Starting Task 2
Starting Task 3
Hello from task 3
Hello from task 1
Hello from task 2
Starting Task 2
Starting Task 1
Starting Task 3
Hello from task 2
Hello from task 1
Hello from task 3
Starting Task 2
Starting Task 3
Starting Task 1
Hello from task 2
Starting Task 2
Hello from task 3
Hello from task 1
Starting Task 3
Starting Task 1
Hello from task 2
Hello from task 1
Starting Task 2
Hello from task 3
...

我正在处理失败的“重新启动”任务的类似问题,我尝试了Theodor Zoulias的回答,但它对我不起作用,因为使用任务本身作为字典键在我的情况下引发了异常(键已经存在)。 我确实使用答案作为我自己解决方案的起点。 这是类似于我想出的东西,它使用字符串将工厂和任务链接在一起:

var taskFactories = new Dictionary<string, Func<Task>>();

taskFactories.Add("task1", () => TaskMethod("task1"));
taskFactories.Add("task2", () => TaskMethod("task2"));
taskFactories.Add("task3", () => TaskMethod("task3"));

//Starts the tasks and stores them in a new dictionary.
var runningTasks = taskFactories.ToDictionary(x => x.Key, x => x.Value());

while (runningTasks.Count > 0)
{
    //Wait for something to happen, good or bad
    var completedTask = await Task.WhenAny(runningTasks.Values);
    string taskId = runningTasks
        .Where(x => x.Value == completedTask)
        .Select(x => x.Key)
        .First();

    if (completedTask.IsFaulted) //Something bad happened
    {
        Func<Task> factory = taskFactories[taskId];
        Task newTask = factory();
        runningTasks.Remove(taskId);
        runningTasks.Add(taskId, newTask);
    }
    else //The task finished normally or was canceled
    {
        runningTasks.Remove(taskId);
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM