多线程应用调试问题

Multithreaded app debug issue

我有一个用 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
              });
         });
     }

你可以尝试添加这个:-

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

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

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

MSDN 有一篇关于 exception handling for the Tasks 的完美文章,您应该通过它找到 select 您自己的异常处理方式。我会在这里重现文章的主要思想,但你建议你阅读整篇文章:

  1. try/catch 块,最容易编写,但如果您有很多任务,在您的代码中 select 放置它的位置可能具有挑战性。请注意,您应该捕获 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. 如果您的代码正在创建一些内部任务(附加或未附加),或者您正在创建任务数组,它们也可以引发异常,您应该检查 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 for the TaskScheduler you are using, similar to one you're trying to handle in AppDomain (event args is an 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();
    }
    

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

In short, exceptions thrown when calling an async void method isn't handled the same way as awaiting a Task and will crash the process. Not a great experience.

尤其不是很好的体验,因为您将无法在调试器中捕获它。可能是你现在遇到的问题。所以我建议你去寻找你的 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 签名来避免将来出现问题。 祝你好运!