使用ExecuteNonQueryAsync和Reporting Progress

时间:2017-08-30 00:23:38

标签: c# sql-server asynchronous

我以为我试图做一些非常简单的事情。我只想在屏幕上报告一个正在运行的号码,以便用户知道我正在执行的SQL存储过程正在运行,并且他们没有耐心并开始点击按钮。

问题是我无法弄清楚如何实际调用ExecutNonQueryAsync命令的进度报告器。它停留在我的报告循环中并且从不执​​行命令但是,如果我把它放在async命令之后,它将被执行并且结果永远不会等于零。

任何想法,评论,想法将不胜感激。非常感谢你!

        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 个答案:

答案 0 :(得分:5)

最简单的方法是使用第二个连接来监控进度并报告。这里有一个小样本可以帮助您入门:

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.

答案 1 :(得分:4)

你无法让ExecuteNonQueryAsync在这里做你想做的事。要执行您要查找的内容,该方法的结果必须是逐行或在SQL调用期间递增的块中,但这不是如何将查询批处理提交到SQL Server的工作方式或实际上您希望如何从头顶上看工作。将SQL语句交给服务器,在完成语句处理后,它返回受语句影响的总行数。

答案 2 :(得分:3)

您是否只是想让用户知道发生了什么事情,而您实际上并不需要显示当前进度?

如果是这样,您只需显示ProgressBar,其Style设置为Marquee

如果您希望这是一种“自包含”方法,您可以在模态表单上显示进度条,并在表单中包含表单代码。

E.g。

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();
}

答案 3 :(得分:3)

这是一个有趣的问题。我过去不得不实施类似的事情。在我们的案例中,优先事项是:

  • 如果用户不想留下来等待,请保持客户端的响应。
  • 更新操作和进度的用户。

我要做的是使用线程在后台运行该过程,如:

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

然后使用一种估计工作时间的方法,我会在时钟上从客户端增加进度条。为此,查询数据中的变量,使其与FunctionThatCallsSQLandTakesTime所需的工作时间成线性关系。

例如;本月活跃用户数驱动FunctionThatCallsSQLandTakesTime占用的时间。每10000个用户需要5分钟。因此,您可以相应地更新进度条。

答案 4 :(得分:1)

我想知道这是否合理:

    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);
    }