多线程应用程序调试问题

本文关键字:问题 调试 应用程序 多线程 | 更新日期: 2023-09-27 18:21:06

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

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

我的问题是,其中一个Task在某个随机点失败,并崩溃并关闭应用程序,而不会出现任何错误,也不会触发断点。我一直无法确定这个问题,因为有很多异步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
              });
         });
     }

多线程应用程序调试问题

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

MSDN有一篇关于任务异常处理的完美文章,您应该仔细阅读它,选择自己处理异常的方式。我将在这里复制文章的主要观点,但您建议您阅读整篇文章:

  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. 如果您的代码正在创建一些内部任务(无论是否附加),或者您正在创建一组任务,它们也可能引发异常,您应该检查AggregateException:的扁平版本

    try {
        task1.Wait();
    }
    catch (AggregateException ae) {
        throw ae.Flatten();
    }
    try {
        Task.WaitAll(tasks.ToArray());
    }
    catch (AggregateException ae) {
        throw ae.Flatten();
    }
    
  4. 使用任务继续过滤故障的任务(注意,异常仍然是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. 如果您仍然缺少异常,请为您正在使用的TaskScheduler使用UnobservedTaskException事件,类似于您试图在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();
    }
    

您可以尝试添加以下内容:-

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

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

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

简而言之,调用异步void方法时抛出的异常与等待Task的处理方式不同,会导致进程崩溃。这不是一次很棒的经历。

特别是这不是一个很好的体验,因为你将无法在调试器中捕捉到它。可能是你现在遇到的问题。所以我建议你去寻找你的async void方法。现在的问题是async void方法可以明显地发现

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

或者隐藏在lambda 后面

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

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

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

这两种方法也能很好地工作,以便通过定期检查async void签名的代码库来避免将来的问题。祝你好运