9

考虑以下代码,它使用带有 CancellationTokenSource 的基本任务库功能。它启动一个线程,用价格填充字典并从 SQL 服务器数据库中读取数据。线程在大约 10 分钟后结束,每 2 小时再次启动,首先调用 Cancel 以防线程仍在运行。

private CancellationTokenSource mTokenSource = new CancellationTokenSource();

internal Prices(Dictionary<string, Dealer> dealers)
{
    mDealers = dealers;
    mTask = Task.Factory.StartNew
            (() => ReadPrices(mTokenSource.Token), mTokenSource.Token);
}

internal void Cancel() 
{
    mTokenSource.Cancel();
}
private void ReadPrices(CancellationToken ct) 
{
     using (SqlConnection connection = 
            new   SqlConnection(ConfigurationManager.AppSettings["DB"]))   
     {
         connection.Open();
         var dealerIds = from dealer in mDealers.Values 
                         where dealer.Id != null 
                         select dealer.Id;
         foreach (var dealerId in dealerIds) 
         {
             if (!ct.IsCancellationRequested)
             {
                 FillPrices(connection);
             }
             else
                break;
        }
    }
}

现在,应用程序在某个时候崩溃,事件日志中出现以下异常。

应用程序:Engine.exe 框架版本:v4.0.30319 描述:进程因未处理的异常而终止。异常信息:System.AggregateException 堆栈:在 System.Threading.Tasks.TaskExceptionHolder.Finalize()

它必须与这里的代码有关,因为任务库没有在其他任何地方使用,但我无法弄清楚代码有什么问题。有谁知道这里可能出了什么问题?

4

1 回答 1

19

任务喜欢被倾听。好像有什么不开心的。但是,您确实有“最后的机会”来听到它:

TaskScheduler.UnobservedTaskException += (sender, args) =>
{
    foreach (var ex in args.Exception.InnerExceptions)
    {
        Log(ex);
    }            
    args.SetObserved();
};

请注意,这不是修复- 它旨在让您看到什么Task正在爆炸以及出现什么错误。这SetObserved()将防止它杀死您的应用程序。但这里的修复是理想的:

  • 不要让你的任务丢掉,
  • 或确保您稍后在那里检查任务的状态

它很可能对您的取消检测不满意。IIRC 的首选方法是:

foreach(...) {
    if(ct.IsCancellationRequested) {
        // any cleanup etc
        ct.ThrowIfCancellationRequested();
    } 
    ...
}

或者更简单地说,如果不需要清理,只需:

foreach(...) {
    ct.ThrowIfCancellationRequested();
    ...
}

同样,它可能只是一个数据访问异常。与数据库交谈时可能会发生许多异常。超时、死锁、无法连接等

于 2013-04-04T06:53:41.930 回答