Как я могу остановить выполнение запроса sql из кода .net?

Если вы рекомендуете использовать SqlCommand.Cancel(), приведите рабочий пример!

Мне нужно показать форму ожидания с кнопкой отмены (если пользователь нажмет эту кнопку, запрос должен прекратить выполнение) во время выполнения запроса.

Мое решение: (.net 4)

Я передаю в конструктор формы два параметра:

  1. задача выполнения запроса
  2. действие отмены

Ниже приведен код моей формы загрузки:

public partial class LoadingFrm : Form
{
     //task for execute query
     private Task execute;
     //action for cancelling task
     private Action cancel;

     public LoadingFrm(Task e, Action c)
     {
         execute = e;
         cancel = c;
         InitializeComponent();
         this.cancelBtn.Click += this.cancelBtn_Click;
         this.FormBorderStyle = FormBorderStyle.None;
         this.Load += (s, ea) =>
         {
             //start task
             this.execute.Start();
             //close form after execution of task
             this.execute.ContinueWith((t) =>
             {
                 if (this.InvokeRequired)
                 {
                     Invoke((MethodInvoker)this.Close);
                 }
                 else this.Close();
             });
         };
      }

      //event handler of cancel button
      private void cancelBtn_Click(object sender, EventArgs e)
      {
          cancel();
      }
}

Ниже код моего класса ExecuteHelper:

public class ExecuteHelper
{
      public static SqlDataReader Execute(SqlCommand command)
      {
          var cts = new CancellationTokenSource();
          var cToken = cts.Token;
          cToken.Register(() => { command.Cancel(); });
          Task<SqlDataReader> executeQuery = new Task<SqlDataReader>(command.ExecuteReader, cToken);
          //create a form with execute task and action for cancel task if user click on button
          LoadingFrm _lFrm = new LoadingFrm(executeQuery, () => { cts.Cancel(); });
          _lFrm.ShowDialog();
          SqlDataReader r = null;
          try
          {
              //(1) here
              r = executeQuery.Result;
          }
          catch (AggregateException ae)
          {
          }
          catch (Exception ex)
          {
          }

          return r;
      }
}

Но я не могу остановить выполнение SqlCommand. Через некоторое время метод, вызывающий ExecuteHelper.Execute(command), получает результат (SqlDataReader) с сервера sql с данными? Почему? Кто-нибудь может помочь? Как я могу отменить выполнение sqlcommand?


И у меня есть еще вопрос. Почему, если я нажму кнопку отмены моей формы, а cts.Cancel() будет вызван //(1) here, я получу executeQuery.IsCanceled = false , хотя executeQuery.Status = Faulted.


person isxaker    schedule 02.07.2014    source источник
comment
@Liath, потому что ссылка не помогает мне решить мою проблему   -  person isxaker    schedule 02.07.2014
comment
@Liath, вы можете дать рабочий код, который останавливает выполнение запроса с помощью метода Cancel SqlCommand?   -  person isxaker    schedule 02.07.2014
comment
Я пробовал это решение, безуспешно.   -  person isxaker    schedule 02.07.2014


Ответы (1)


Вместо вызова ExecuteReader вызовите ExecuteReaderAsync и передайте CancellationToken.

Если вы используете .net 4.0, вы можете написать свой собственный ExecuteReaderAsync, используя файл TaskCompletionSource. Я не тестировал этот код, но он должен быть примерно таким:

public static class Extensions
 {
        public static Task<SqlDataReader> ExecuteReaderAsync(this SqlCommand command, CancellationToken token)
        {   
            var tcs = new TaskCompletionSource<SqlDataReader>();

            // whne the token is cancelled, cancel the command
            token.Register( () => 
            {
                command.Cancel();
                tcs.SetCanceled();
            });

            command.BeginExecuteReader( (r) =>
            {
                try
                {
                    tcs.SetResult(command.EndExecuteReader(r));
                }
                catch(Exception ex)
                {
                    tcs.SetException(ex);
                }
            }, null);

            return tcs.Task;
        }
 }

Вы используете метод SqlCommand.Cancel для отмены любой выполняемой асинхронной операции. Затем вы можете использовать его следующим образом:

 public static SqlDataReader Execute(SqlCommand command)
 {
     SqlDataReader r = null;
     var cts = new CancellationTokenSource();
     var cToken = cts.Token;
     var executeQuery = command.ExecuteReaderAsync(cToken).
                                                    .ContinueWith( t =>
                                                    {
                                                        if(t.IsCompleted)
                                                        {
                                                            r = t.Result;       
                                                        }       
                                                    }, TaskScheduler.Default);

     //create a form with execute task and action for cancel task if user click on button
     LoadingFrm _lFrm = new LoadingFrm(executeQuery, () => { cts.Cancel(); });

     // Assuming this is blocking and that the executeQuery will have finished by then, otheriwse
     // may need to call executeQuery.Wait().
     _lFrm.ShowDialog();                            

     return r;
 }

Я изменил метод Execute, чтобы использовать ContunueWith, а не r.Result, потому что Result является блокирующим свойством, и диалоговое окно не будет отображаться до тех пор, пока запрос не будет завершен. Как уже упоминалось, он не проверен, но должен быть довольно близок к тому, что вам нужно.

person NeddySpaghetti    schedule 02.07.2014
comment
.net 4 не имеет ExecuteReaderAsync - person isxaker; 02.07.2014
comment
только BeginExecuteReader и EndExecuteReader - person isxaker; 02.07.2014
comment
Вы можете сделать его самостоятельно, используя Task.FromAsync. Я опубликую код. - person NeddySpaghetti; 02.07.2014
comment
Я обновил свой ответ, надеюсь, это поможет. - person NeddySpaghetti; 02.07.2014
comment
попробуйте изменить тип переменной executeQuery на var - person NeddySpaghetti; 02.07.2014
comment
я не могу получить результат из запроса sql. Код из формы, закрывающей форму, не выполняется. - person isxaker; 02.07.2014
comment
Я смогу поговорить об этом после работы, но если вам нужно executeQuery вернуть SqlDataReader, попробуйте изменить ContinueWith на ContinueWith<SqlDataReader> - person NeddySpaghetti; 03.07.2014
comment
@ned-stoyanov Можете ли вы обновить свое решение, включив в него код при успешном запуске для автоматического закрытия LoadingFrm, т. е. LoadingFrm не получает сигнала о закрытии, если только пользователь не нажмет кнопку «Отмена», поэтому вы останетесь в зависании. Я нашел обходной путь для перехвата ObjectDisposedException при вызове метода Close после строки r = t.result, но, безусловно, должен быть более элегантный способ. - person Glen; 31.07.2017