【问题标题】:Multithreaded app debug issue多线程应用程序调试问题
【发布时间】:2015-11-09 07:43:04
【问题描述】:

我有一个用 Mono (Xamarin) 开发的多线程 .Net 应用程序,有很多后台异步运行 Tasks

public Task UpdateAsync()
{
    return Task.Run (() => {
        .....
    });
}

我的问题是其中一个任务在某个随机点失败并崩溃并关闭应用程序而没有任何错误并且没有断点触发。我无法查明这个问题,而且真的很难,因为有很多运行异步 Tasks

有没有办法找到问题所在的方法和线路,或者在那个时候更好地解决问题?

编辑: 我也尝试按照下面的建议注册UnhandledException,但它仍然没有处理任何错误,应用程序只是关闭而没有任何痕迹

AppDomain.CurrentDomain.UnhandledException += (o, e) =>{ Debugger.Break(); }

编辑2: 感谢这里的所有帮助,我终于找到了问题。是否可以通过更改下面的代码来建议一种方法来防止这种情况(使调试器中断,而不是应用程序崩溃)?

    public Task StagedUpdateAsync() 
    {

        return Task.Run (() => {
             .
             .
             .
           InvokeOnMainThread (() => 
              {
                   // somehow here it was trying to use a null object
                   // and application crashed
              });
         });
     }

【问题讨论】:

    标签: c# multithreading mono xamarin.ios xamarin.android


    【解决方案1】:

    首先,我想指出,任务本身不会从其内部代码中引发异常,直到直接要求它们提供 Result 属性或 Wait* method 或任何其他阻塞方法,所以这是完美的地方搜索异常是代码的结果部分。

    MSDN 有一篇关于exception handling for the Tasks 的完美文章,您应该通过它来选择自己的方式来处理异常。我将在此处复制文章中的主要思想,但您建议您阅读整篇文章:

    1. try/catch 块,最容易编写,但如果你有很多任务,在你的代码中选择一个位置可能会很有挑战性。请注意,您应该捕获 AggregateException 作为内部异常的包装器,如下所示:

      var task1 = Task.Run( () => { throw new CustomException("This exception is expected!"); } );
      
      try
      {
          task1.Wait();
      }
      catch (AggregateException ae)
      {
          // foreach here
      }
      
    2. 等待任务完成并检查其状态:

      var task1 = Task.Run( () => { throw new CustomException("This exception is expected!"); } );
      
      while(! task1.IsCompleted) {}
      if (task1.Status == TaskStatus.Faulted)
      {
          // foreach here
      }
      
    3. 如果您的代码正在创建一些内部任务(attached or not),或者您正在创建任务数组,它们也会引发异常,您应该检查 AggregateException 的扁平化版本:

      try {
          task1.Wait();
      }
      catch (AggregateException ae) {
          throw ae.Flatten();
      }
      
      try {
          Task.WaitAll(tasks.ToArray());
      }
      catch (AggregateException ae) {
          throw ae.Flatten();
      }
      
    4. 使用tasks continuation 过滤有故障的(注意异常仍然是AggregateException 之一:

      var task1 = Task.Run(() =>
                             { throw new CustomException("task1 faulted.");
      }).ContinueWith(t => { Console.WriteLine("{0}: {1}",
          t.Exception.InnerException.GetType().Name,
          t.Exception.InnerException.Message);
      }, TaskContinuationOptions.OnlyOnFaulted);
      
    5. 如果您仍然没有发现异常,请使用 UnobservedTaskException event 作为您正在使用的 TaskScheduler,类似于您在 AppDomain 中尝试处理的异常(事件参数是 UnobservedTaskExceptionEventArgs):

      TaskScheduler.Default.UnobservedTaskException += (o, e) => {
          Console.WriteLine(e.Exception.ToString());
          Debugger.Break();
      }
      // or
      TaskScheduler.Current.UnobservedTaskException += (o, e) => {
          Console.WriteLine(e.Exception.ToString());
          Debugger.Break();
      }
      

    【讨论】:

    • 这非常详细,并为我解释了很多关于任务的内容。谢谢VMAtm
    • 如果 UnobservedTaskException 没有触发,那么可以肯定地说问题出在其他地方吗?我怀疑本地库调用中的内存泄漏或崩溃?
    • @iamIcarus 是的,我认为这个问题与Tasks 无关。可能是OutOfMemoryException,然后您必须重新设计您的应用程序,因为在这种异常情况下没有机会执行代码。
    • 我已经用我最终发现问题的代码更新了原始问题。是否可以通过更改我引用的代码来提出一种防止这种情况的方法?
    【解决方案2】:

    你可以尝试添加这个:-

     AppDomain.CurrentDomain.UnhandledException += (o,e) =>{ Debugger.Break();}
    

    然后检查 e 以查看异常是什么,您应该能够打开线程窗口并切换到导致问题的线程,然后使用调用堆栈返回。

    【讨论】:

    • 感谢您的建议,刚刚尝试过,我真的希望它能工作,但我仍然会崩溃,没有任何断点命中。该应用程序刚刚关闭
    【解决方案3】:

    对我来说,这听起来很像async void 问题。阅读它here,它基本上说:

    简而言之,调用 async void 方法时抛出的异常与等待 Task 的处理方式不同,并且会使进程崩溃。不是很好的体验。

    尤其不是很好的体验,因为您无法在调试器中捕捉到它。可能是你现在遇到的问题。所以我建议你去寻找你的async void 方法。现在的问题是async void 方法很容易被发现

    public async void Foo()
    {
       await Task.Run(() => {});
    }
    

    或者很好地隐藏在 lambda 后面

    Action foo = async () => await Task.Run(() => {});
    

    因此,在更大的代码库中标记它们成为一项相当乏味的任务。幸运的是,前面提到的article 的作者提供了一种基于反射搜索async void 签名的自动化解决方案。去看看吧。

    如果您使用的是 Visual Studio 2015,您还可以使用基于 Roslyn 的代码分析器。 GitHub 上有一个专门用于 async/await 的。

    通过定期检查代码库中的async void 签名,这两种方法也很有效,以避免将来出现问题。 祝你好运!

    【讨论】:

    • 这对我来说其实是有点希望!谢谢,一定会阅读文章并尝试一下。
    • 我已经用我最终发现问题的代码更新了原始问题。是否可以通过更改我引用的代码来提出一种防止这种情况的方法?
    猜你喜欢
    • 2013-04-17
    • 1970-01-01
    • 2010-10-11
    • 2022-01-02
    • 1970-01-01
    • 2010-09-21
    • 2011-03-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多