Использование ExecuteNonQueryAsync и отчет о ходе работы

Я думал, что я пытаюсь сделать что-то очень простое. Я просто хочу сообщить рабочий номер на экране, чтобы пользователь понял, что хранимая процедура SQL, которую я выполняю, работает, и что они не теряют терпения и начинают нажимать кнопки.

проблема в том, что я не могу понять, как на самом деле вызвать progress reporter для команды ExecutNonQueryAsync. Он застревает в моем цикле отчетов и никогда не выполняет команду, но, если я положу ее после асинхронная команда, она будет выполнена, и результат никогда не будет равен нулю.

любые мысли, комментарии, идеи будут оценены. Большое вам спасибо!

        int i = 0;
        lblProcessing.Text = "Transactions " + i.ToString();
        int result = 0;
        while (result==0)
        {
            i++;
            if (i % 500 == 0)
            {
                lblProcessing.Text = "Transactions " + i.ToString();
                lblProcessing.Refresh();
            }

        }
        //  Yes - I know - the code never gets here - that is the problem! 
        result = await cmd.ExecuteNonQueryAsync();

5 ответов


вы просто хотите, чтобы пользователь знал, что что-то происходит, и вам на самом деле не нужно отображать текущий прогресс?

если это так, вы можете просто отобразить ProgressBar С Style значение Marquee.

если вы хотите, чтобы это был" автономный " метод, вы можете отобразить индикатор выполнения в модальной форме и включить код формы в сам метод.

Э. Г.

public void ExecuteNonQueryWithProgress(SqlCommand cmd) {
    Form f = new Form() {
        Text = "Please wait...",
        Size = new Size(400, 100),
        StartPosition = FormStartPosition.CenterScreen,
        FormBorderStyle = FormBorderStyle.FixedDialog,
        MaximizeBox = false,
        ControlBox = false
    };
    f.Controls.Add(new ProgressBar() { 
        Style = ProgressBarStyle.Marquee,
        Dock = DockStyle.Fill
    });
    f.Shown += async (sender, e) => {
        await cmd.ExecuteNonQueryAsync();
        f.Close();
    };
    f.ShowDialog();
}

вы не сможете заставить ExecuteNonQueryAsync делать то, что вы хотите здесь. Чтобы сделать то, что вы ищете, результат метода должен быть либо строкой за строкой, либо кусками, увеличенными во время вызова SQL, но это не то, как отправка пакета запроса на SQL Server работает или действительно как вы хотите, чтобы он работал с точки зрения накладных расходов. Вы передаете инструкцию SQL серверу, и после завершения обработки инструкции она возвращает общее количество строк затронуты заявлением.


самый простой способ сделать это-использовать второе соединение для мониторинга прогресса и отчета о нем. Вот небольшой пример, чтобы вы начали:

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Text;
using System.Threading.Tasks;

namespace Microsoft.Samples.SqlServer
{
    public class SessionStats
    {
        public long Reads { get; set; }
        public long Writes { get; set; }
        public long CpuTime { get; set; }
        public long RowCount { get; set; }
        public long WaitTime { get; set; }
        public string LastWaitType { get; set; }
        public string Status { get; set; }

        public override string ToString()
        {
            return $"Reads {Reads}, Writes {Writes}, CPU {CpuTime}, RowCount {RowCount}, WaitTime {WaitTime}, LastWaitType {LastWaitType}, Status {Status}";
        }
    }
    public class SqlCommandWithProgress
    {


        public static async Task ExecuteNonQuery(string ConnectionString, string Query, Action<SessionStats> OnProgress)
        {
            using (var rdr = await ExecuteReader(ConnectionString, Query, OnProgress))
            {
                rdr.Dispose();
            }
        }

        public static async Task<DataTable> ExecuteDataTable(string ConnectionString, string Query, Action<SessionStats> OnProgress)
        {
            using (var rdr = await ExecuteReader(ConnectionString, Query, OnProgress))
            {
                var dt = new DataTable();

                dt.Load(rdr);
                return dt;
            }
        }


        public static async Task<SqlDataReader> ExecuteReader(string ConnectionString, string Query, Action<SessionStats> OnProgress)
        {
            var mainCon = new SqlConnection(ConnectionString);
            using (var monitorCon = new SqlConnection(ConnectionString))
            {
                mainCon.Open();
                monitorCon.Open();



                var cmd = new SqlCommand("select @@spid session_id", mainCon);
                var spid = Convert.ToInt32(cmd.ExecuteScalar());

                cmd = new SqlCommand(Query, mainCon);

                var monitorQuery = @"
select s.reads, s.writes, r.cpu_time, s.row_count, r.wait_time, r.last_wait_type, r.status
from sys.dm_exec_requests r
join sys.dm_exec_sessions s 
  on r.session_id = s.session_id
where r.session_id = @session_id";

                var monitorCmd = new SqlCommand(monitorQuery, monitorCon);
                monitorCmd.Parameters.Add(new SqlParameter("@session_id", spid));

                var queryTask = cmd.ExecuteReaderAsync( CommandBehavior.CloseConnection );

                var cols = new { reads = 0, writes = 1, cpu_time =2,row_count = 3, wait_time = 4, last_wait_type = 5, status = 6 };
                while (!queryTask.IsCompleted)
                {
                    var firstTask = await Task.WhenAny(queryTask, Task.Delay(1000));
                    if (firstTask == queryTask)
                    {
                        break;
                    }
                    using (var rdr = await monitorCmd.ExecuteReaderAsync())
                    {
                        await rdr.ReadAsync();
                        var result = new SessionStats()
                        {
                            Reads = Convert.ToInt64(rdr[cols.reads]),
                            Writes = Convert.ToInt64(rdr[cols.writes]),
                            RowCount = Convert.ToInt64(rdr[cols.row_count]),
                            CpuTime = Convert.ToInt64(rdr[cols.cpu_time]),
                            WaitTime = Convert.ToInt64(rdr[cols.wait_time]),
                            LastWaitType = Convert.ToString(rdr[cols.last_wait_type]),
                            Status = Convert.ToString(rdr[cols.status]),
                        };
                        OnProgress(result);

                    }

                }
                return queryTask.Result;


            }
        }
    }
}

который вы бы назвали что-то вроде этого:

    class Program
    {

        static void Main(string[] args)
        {
            Run().Wait();

        }
        static async Task Run()
        {
            var constr = "server=localhost;database=tempdb;integrated security=true";
            var sql = @"
set nocount on;
select newid() d
into #foo
from sys.objects, sys.objects o2, sys.columns 
order by newid();
select count(*) from #foo;
";

            using (var rdr = await SqlCommandWithProgress.ExecuteReader(constr, sql, s => Console.WriteLine(s)))
            {
                if (!rdr.IsClosed)
                {
                    while (rdr.Read())
                    {
                        Console.WriteLine("Row read");
                    }
                }
            }
            Console.WriteLine("Hit any key to exit.");
            Console.ReadKey();


        }
    }

выходы:

Reads 0, Writes 0, CPU 1061, RowCount 0, WaitTime 0, LastWaitType SOS_SCHEDULER_YIELD, Status running
Reads 0, Writes 0, CPU 2096, RowCount 0, WaitTime 0, LastWaitType SOS_SCHEDULER_YIELD, Status running
Reads 0, Writes 0, CPU 4553, RowCount 11043136, WaitTime 198, LastWaitType CXPACKET, Status suspended
Row read
Hit any key to exit.

Это интересный вопрос. Мне приходилось реализовывать подобные вещи в прошлом. В нашем случае приоритетом было:

  • держите клиентскую сторону отзывчивой, если пользователь не хочет оставаться и ждать.
  • обновление пользователя действия и прогресса.

что я бы сделал, это использовать threading для запуска процесса в фоновом режиме, например:

HostingEnvironment.QueueBackgroundWorkItem(ct => FunctionThatCallsSQLandTakesTime(p, q, s));

затем, используя способ оценки рабочего времени, я бы увеличил индикатор выполнения клиентская сторона на часах. Для этого запросите данные для переменной, которая дает вам линейную связь с рабочим временем, необходимым FunctionThatCallsSQLandTakesTime.

например; количество активных пользователей в этом месяце управляет временем FunctionThatCallsSQLandTakesTime принимает. Для каждого пользователя 10000 требуется 5 минут. Таким образом, вы можете обновить индикатор выполнения соответственно.


Мне интересно, может ли это быть разумным подходом:

    IAsyncResult result = cmd2.BeginExecuteNonQuery();
    int count = 0;
    while (!result.IsCompleted)
    {
         count++;
         if (count % 500 == 0)
         {
            lblProcessing.Text = "Transactions " + i.ToString();
            lblProcessing.Refresh();
         }
         // Wait for 1/10 second, so the counter
         // does not consume all available resources 
         // on the main thread.
         System.Threading.Thread.Sleep(100);
    }