代码从捕获中掉出来了吗?

时间:2018-08-02 10:52:10

标签: c# sql-server async-await system.reactive .net-4.6.2

我有一个后台任务,每200毫秒轮询一次SQL Server数据库。

代码如下:

listener = await Task.Factory.StartNew(async () =>
            {
                try
                {
                    while (true)
                    {
                        topToken.ThrowIfCancellationRequested();

                        try
                        {
                            using (var dbConnection = new SqlConnection(ConnectionString))
                            using (var command = new SqlCommand("marc.GetEvents", dbConnection))
                            {
                                await command.Connection.OpenAsync().ConfigureAwait(false);
                                command.CommandType = CommandType.StoredProcedure;
                                command.Parameters.AddWithValue("@fromId", lastEventId);

                                using (var reader = await command.ExecuteReaderAsync(topToken).ConfigureAwait(false))
                                {
                                    int received = lastEventId;
                                    while (await reader.ReadAsync(topToken).ConfigureAwait(false))
                                    {
                                        /// do stuff...
                                    }
                                    lastEventId = received;
                                }
                            }
                            await Task.Delay(PollIntervalMilliseconds, topToken).ConfigureAwait(false);
                        }
                        catch (OperationCanceledException)
                        {
                            throw;
                        }
                        catch (Exception ex)
                        {
                            if (ex is SqlException && topToken.IsCancellationRequested)
                            {
                                throw new OperationCanceledException("Operation cancelled by user", ex);
                            }

                            logger.Warn(ex, $"Exception on polling Codeks db. Waiting {delayOnSqlError}ms..."); // this is hit
                            _OnReaderEvent.OnError(ex);
                            await Task.Delay(delayOnSqlError, topToken).ConfigureAwait(false); // probably not executed
                        }
                    }
                }
                catch (OperationCanceledException)
                {
                    logger.Info("Listening task ended. Service is stopping?");
                }
                catch (Exception ex)
                {
                    logger.Error(ex, "General exception"); // falling here
                }
            }, TaskCreationOptions.LongRunning).ConfigureAwait(false);

今天,我收到有关此任务过早结束这一事实的报告。根据日志,第一个catch集被命中,并报告SQL异常:

  

2018-08-01 17:42:08.6348 |警告|轮询Codeks数据库时发生异常。等待5000毫秒... System.Data.SqlClient.SqlException(0x80131904):事务(进程ID 53)在锁定时死锁|与另一个进程通信缓冲资源,并已被选为死锁受害者。重新运行事务。

但没有延迟,它是从循环中立即掉到外部catch上的,这完全相同。

  

2018-08-01 17:42:08.6488 |错误| Jantar.CodeksConnector |常规异常System.Data.SqlClient.SqlException(0x80131904):事务(进程ID 53)在锁定时死锁|与另一个进程通信缓冲资源,并已被选为死锁受害者。重新运行事务。      在System.Data.SqlClient.SqlConnection.OnError(SqlException异常,布尔值breakConnection,操作1 wrapCloseInAction) at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action 1 wrapCloseInAction)      在System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj,布尔调用方HasConnectionLock,布尔asyncClose)      在System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior,SqlCommand cmdHandler,SqlDataReader dataStream,BulkCopySimpleResultSet bulkCopyHandler,TdsParserStateObject stateObj,Boolean和dataReady)      在System.Data.SqlClient.SqlDataReader.TryHasMoreRows(Boolean&moreRows)      在System.Data.SqlClient.SqlDataReader.TryReadInternal(Boolean setTimeout,Boolean&更多)      在System.Data.SqlClient.SqlDataReader处。<> c__DisplayClass189_0.b__0(任务t)      在System.Data.SqlClient.SqlDataReader.InvokeRetryable [T](Func {2 moreFunc, TaskCompletionSource 1来源,IDisposable objectToDispose)处   ---从之前引发异常的位置开始的堆栈结束跟踪---      在System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(任务任务)      在System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(任务任务)      在System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(任务任务)      在Jantar.CodeksConnector。 d.MoveNext()   ---从之前引发异常的位置开始的堆栈结束跟踪---      在System.Reactive.PlatformServices.ExceptionServicesImpl.Rethrow(Exception异常)      在System.Reactive.Stubs。<> c。<。cctor> b__2_1(异常异常)      在System.Reactive.AnonymousSafeObserver 1.OnError(Exception error) at System.Reactive.Linq.ObservableImpl.SelectMany 2. .OnError(异常错误)处      在System.Reactive.Linq.ObservableImpl.Where 1._.OnError(Exception error) at System.Reactive.Linq.ObservableImpl.AsObservable 1。 .OnError(异常错误)      在System.Reactive.Observer 1.OnError(Exception error) at System.Reactive.Subjects.Subject 1.OnError(异常错误)处      在Jantar.CodeksConnector中。 d.MoveNext()

我没主意了...

[更新:08.03]

@sellotape为我指明了正确的方向。根据更新后的第二个日志条目,stacktrace清楚地表明Subject<T>.onError(ex)抛出了异常(我将其删除是因为它是一个错误)。这是一个双重错误,因为没有错误订阅者。我不知道在这种情况下,只有在有任何订户的情况下,该异常才会被重新引发,而在没有订户的情况下,该异常将被吞下。

1 个答案:

答案 0 :(得分:0)

虽然这不是您问题的直接答案,但是您已将其标记为“ System.Reactive”,因此我想我可能会(大致)向您展示代码的Rx解决方案。请记住,我无法准确地提供/// do stuff...的代码,所以我做了。

这里是Rx:

IObservable<string> query =
    from t in Observable.Interval(TimeSpan.FromMilliseconds(PollIntervalMilliseconds))
    from x in Observable.Using(
        () => new SqlConnection(ConnectionString),
        dbConnection =>
            Observable.Using(
                () =>
                {
                    var c = new SqlCommand("marc.GetEvents", dbConnection);
                    c.CommandType = CommandType.StoredProcedure;
                    c.Parameters.AddWithValue("@fromId", lastEventId);
                    return c;
                },
                command =>
                    from o in Observable.FromAsync(() => command.Connection.OpenAsync())
                    from reader in Observable.FromAsync(() => command.ExecuteReaderAsync(topToken))
                    let received = lastEventId
                    from r in Observable.FromAsync(() => reader.ReadAsync(topToken))
                    select reader.GetFieldValue<string>(0)))
    select x;