如何确保操作不会使整个应用程序崩溃?

时间:2019-08-21 11:43:54

标签: c# .net exception crash

我有一个应用程序执行一些其他工作,例如清除旧日志,发送通知等。如果一项工作失败,我不希望整个应用程序停止工作并且不执行剩下的工作。

例如,

await SendUsersBirthdayEmailsAsync(); // <-- if something fails while trying to send birthday emails here, I don't want the app to stop working and not clean logs and so on...
await DeleteOutdatedLogsAsync();
await SendSystemNotificationsAsync();

您会推荐我去哪儿?

11 个答案:

答案 0 :(得分:8)

在可能失败的代码的每个部分上使用try-catch块。
根据需要,使用try-catch-finally块。

在每个catch块上,记录所需的异常。我使用Nlog进行日志记录,因此建议您进行调查。

try{
    //do work here
}
catch(Exception e){
    //log exception here
}
//optional
finally{
    //do optional needed work here
}

类似这样的东西:

public bool SendUsersBirthdayEmailsAsync(){
    try{
        SendMail();
    }
    catch(Exception e){
        LogException(e);
    }
    //optional
    finally{
        OptionalWork();
    }       
}

编辑:关于避免使用泛型异常

您可以始终使用多个catch块,每个块为每种类型的异常定义不同的行为。当您知道可以预期的异常类型时,这很有用。
示例:

public bool SendUsersBirthdayEmailsAsync(){
    try{
        SendMail();
    }
    catch (ThreadAbortException tae)
    {
        LogException(tae);
        //do something specific
    }
    catch (ThreadInterruptedException tie)
    {
        LogException(tie);
        //do something specific
    }
    catch(Exception e){
        LogException(e);
    }
    //optional
    finally{
        OptionalWork();
    }       
}

编辑2:Official Microsoft guidance用于异常处理。

  

在可能潜在生成异常的代码周围使用try/catch块,您的代码可以从该异常中恢复。在catch块中,始终按从最高派生到最低派生的顺序对异常进行排序。所有异常均源自Exception。对于基本异常类,更多的派生异常不会由catch子句处理,而catch子句会在其后。当您的代码无法从异常中恢复时,请不要捕获该异常。启用方法使调用堆栈更远,以便在可能的情况下恢复。

     

清理用using语句或finally块分配的资源。首选using语句以在引发异常时自动清理资源。使用finally块来清理未实现IDisposable的资源。 finally子句中的代码几乎总是执行,即使抛出异常也是如此。

答案 1 :(得分:0)

Task<Task> task = (SendUsersBirthdayEmailsAsync()
            .ContinueWith(x => Task.WhenAll(DeleteOutdatedLogsAsync(), SendSystemNotificationsAsync())));
await await task;

有关更一般的示例,请提供以下代码:

class TaskTest
{
    public async void Start()
    {

        await (
            (await One().ContinueWith(x => Task.WhenAll(Two(), Three())))
            .ContinueWith(x=> Four()));
    }

    private async Task One()
    {
        await Task.Delay(5000);
        Console.WriteLine("1");
        throw new Exception();
    }

    private async Task Two()
    {
        await Task.Delay(2000);
        Console.WriteLine("2");
        throw new Exception();

    }
    private async Task Three()
    {
        await Task.Delay(3000);
        Console.WriteLine("3");
        throw new Exception();
    }

    private async Task Four()
    {
        await Task.Delay(1000);
        Console.WriteLine("4");
        throw new Exception();
    }
}

运行这段代码表明,在任务内部引发异常不会停止整个程序。

答案 2 :(得分:0)

如果您已经使用调度程序进行了操作,为什么不将其分成较小的任务呢?这样,如果一项操作失败,它将不会降低所有其他操作。还有一个好习惯是让许多较小的应用程序承担专门的责任,而不是通用的应用程序负责一切。

答案 3 :(得分:0)

如果您正在寻找一种可靠的方法来确保该过程已完成且可追溯,则可以选择Hangfire。您还可以通过处理异常来将重试逻辑放在失败的情况下。

答案 4 :(得分:0)

由于方法的名称表明这些方法彼此独立,因此只需确保每个方法都返回一个可等待的任务并将它们放入列表即可。您对List<Task> tasks = new List<Task>(); try { tasks.Add(SendUsersBirthdayEmailsAsync()); tasks.Add(DeleteOutdatedLogsAsync()); tasks.Add(SendSystemNotificationsAsync()); Task.WhenAll(tasks); // waits for all tasks to finish } catch (Exception e) { //Log e.ToString(); will give you all inner exceptions of the aggregate exception as well, incl. StackTraces, so expect possibly a lot of chars, wherever you log it. } 的使用表明它们已经这样做了。

在其周围放置一个try-catch并捕获AggregateExeption。

{{1}}

这可能还会加快速度,因为它们现在正在并行运行。但是,由于它们似乎都可以在数据库上工作(很可能是相同的),所以由于调度开销,它可能不会太多(如果有的话)。

答案 5 :(得分:0)

您可以使用Try-Pattern实现方法,并使它们返回布尔值。 让每个方法自己处理异常,以便确保不会引发任何未处理的异常以杀死应用程序。

万一失败将导致程序状态无法恢复,则方法应返回false,应用程序应以干净的方式自行退出。

答案 6 :(得分:0)

未观察到的任务异常

await SendUsersBirthdayEmailsAsync(); // uses TaskScheduler

由于您正在使用TaskScheduler,请查看TaskScheduler.UnobservedTaskException

  

在错误任务的未观察到的异常即将触发时发生   异常升级策略,默认情况下会终止   过程

答案 7 :(得分:0)

您需要在每个函数中使用Try-Catch,如下所示。

try{
    //write your code
}
catch(Exception e){
    //your exception will be here
}

您可以为该异常生成日志文件。 只需为您的日志文件做一件事。创建一个日志类和一个日志文件生成函数。 在所有函数的捕获区域中调用该函数。

让您创建一个名称为 clsLog 的Log类。和名称为 InsertLog(字符串异常,字符串函数名)的静态函数。

在所有功能中使用此日志方法,如下所示。

public void insertcity()
{
 try
 {
   //Insert city programming
 }
 catch (exception ex)
 {
   clsLog.InsertLog(ex.Message,"insertCity");
   //do something else you want.
 }        
}

希望这会有所帮助。

答案 8 :(得分:0)

answer所使用的Andreas符合我的想法-但是我会再做一个-取消令牌-对您认为足够时间有时间限制-所以一切都完成了。

“等待”中的各个任务意味着它们在分开执行时,此时又回到了一起。如果发生异常,您可以捕获这些异常-然后确定是否超时-或需要记录的异常。

还应包含有关UnhandledException和UnhandledTask异常的两个建议-出于何种原因它们都会停止。

我还考虑将其作为Windows服务,以便您可以在启动时将取消令牌传递到任务中-并在服务实际运行时使用计时器-而不是计划任务-然后使用所有异常处理您可以看到日志随着时间的推移告诉您的信息-对我来说,这感觉比控制台应用程序更具弹性-但这就是我们通常以这种方式运行任务的方式。

您可以在控制台应用程序中启动和停止这样的服务以进行测试-然后将其交给处理OnStart和OnStop事件的项目-在中传递Cancellation令牌-OnStop将取消该令牌并停止该服务循环。

答案 9 :(得分:0)

我认为您是否使用

try{
    //your nice work
}
catch(Exception e){
    //hmm.. error ok i will show u
}
//optional
finally{
    //again start the operation if u failed here.
}

我还创建了相同的应用程序,当出现一些错误并失败了,然后在catch异常中时,我进行了登录,并最终再次重新启动了该应用程序。所以它永远不会死。

答案 10 :(得分:0)

我建议您找出从async得到的异常,以实际获得此异常,并了解可以在主要Thread上使用的异常:

MyTask().GetAwaiter().GetResult();

这将允许您查看在await线程上遇到的实际异常的文本。


还有一点,如果您不希望主线程返回到await行,您还可以使用:

await SendUsersBirthdayEmailsAsync().ConfigureAwait(false);

这只会在最近的免费线程上继续执行您的任务。

但是需要说的是,这会在Android上引起异常,因为它对使用线程有一些限制。


如果您不想浪费时间或不需要手动处理异常并对其进行修复,trycatch finally始终是一个选择。 / p>