Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Queryasync method CancellationToken request cancellation, DAPPER will not immediately terminate execution #1640

Open
slqd3781 opened this issue Mar 29, 2021 · 2 comments

Comments

@slqd3781
Copy link

Hello, author: I am a developer from China. My English is not very good, so my question is translated using translation software, so the meaning may not be very accurate.
When I used asynchronous query, I found a small problem. The source code is as follows

private static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, Type effectiveType, CommandDefinition command)
        {
            object param = command.Parameters;
            var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType());
            var info = GetCacheInfo(identity, param, command.AddToCache);
            bool wasClosed = cnn.State == ConnectionState.Closed;
            var cancel = command.CancellationToken;
            using var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader);
            if (cancel.CanBeCanceled)
            {
                cancel.Register((obj) => ((DbCommand)obj)?.Cancel(), cmd);
            }
            DbDataReader reader = null;
            try
            {
                if (wasClosed) await cnn.TryOpenAsync(cancel).ConfigureAwait(false);
                reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, cancel).ConfigureAwait(false);

                var tuple = info.Deserializer;
                int hash = GetColumnHash(reader);
                if (tuple.Func == null || tuple.Hash != hash)
                {
                    if (reader.FieldCount == 0)
                        return Enumerable.Empty<T>();
                    tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false));
                    if (command.AddToCache) SetQueryCache(identity, info);
                }

                var func = tuple.Func;

                if (command.Buffered)
                {
                    var buffer = new List<T>();
                    var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType;
                    while (await reader.ReadAsync(cancel).ConfigureAwait(false))
                    {
                        object val = func(reader);
                        buffer.Add(GetValue<T>(reader, effectiveType, val));
                    }
                    while (await reader.NextResultAsync(cancel).ConfigureAwait(false)) { /* ignore subsequent result sets */ }
                    command.OnCompleted();
                    return buffer;
                }
                else
                {
                    // can't use ReadAsync / cancellation; but this will have to do
                    wasClosed = false; // don't close if handing back an open reader; rely on the command-behavior
                    var deferred = ExecuteReaderSync<T>(reader, func, command.Parameters);
                    reader = null; // to prevent it being disposed before the caller gets to see it
                    return deferred;
                }
            }
            finally
            {
                using (reader) { /* dispose if non-null */ }
                if (wasClosed)
                    cnn.Close();
            }
        }

When I execute a time-consuming SQL statement, if the request is canceled, that is, the value of CancellationToken.IsCancellationRequested becomes true, then the try will be jumped out and the finally block will be executed. When the reader.dispose under finally is executed, the execution will be blocked until The SQL statement is completely executed before it will continue to execute. What I think is that the request to cancel finally will not block.
Then I checked the source code from the Internet and found a solution:

 private static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, Type effectiveType, CommandDefinition command)
        {
            object param = command.Parameters;
            var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType());
            var info = GetCacheInfo(identity, param, command.AddToCache);
            bool wasClosed = cnn.State == ConnectionState.Closed;
            var cancel = command.CancellationToken;
            using var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader);
            if (cancel.CanBeCanceled)
            {
                cancel.Register((obj) => ((DbCommand)obj)?.Cancel(), cmd);
            }
            DbDataReader reader = null;
            try
            {
                if (wasClosed) await cnn.TryOpenAsync(cancel).ConfigureAwait(false);
                reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, cancel).ConfigureAwait(false);

                var tuple = info.Deserializer;
                int hash = GetColumnHash(reader);
                if (tuple.Func == null || tuple.Hash != hash)
                {
                    if (reader.FieldCount == 0)
                        return Enumerable.Empty<T>();
                    tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false));
                    if (command.AddToCache) SetQueryCache(identity, info);
                }


After adding cancel.Register((obj) => ((DbCommand)obj)?.Cancel(), cmd);, the program will execute smoothly

@jecacs
Copy link

jecacs commented Apr 30, 2021

Same

@NickCraver
Copy link
Member

Please follow #1590 for this one!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants