【问题标题】:Using ExecuteNonQueryAsync and Reporting Progress使用 ExecuteNonQueryAsync 和报告进度
【发布时间】:2017-08-30 00:23:37
【问题描述】:

我以为我正在尝试做一些非常简单的事情。我只想在屏幕上报告一个运行数字,以便用户知道我正在执行的 SQL 存储过程正在工作并且他们不会不耐烦并开始单击按钮。

问题是我不知道如何实际调用 ExecutNonQueryAsync 命令的进度报告器。它卡在我的报告循环中并且从不执​​行命令,但是如果我将它放在异步命令之后,它将被执行并且结果永远不会不为零。

任何想法、cmets、想法将不胜感激。非常感谢!

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

【问题讨论】:

  • 你的循环永远不会退出,因为result 总是0。该循环中的任何内容都不会改变它。
  • 是的 - 我知道。问题是我不知道如何使用 ExecuteNonQueryAsync() 进行某种进度报告
  • 据我所知,没有任何方法可以让 SQL Server 向您报告进度。
  • 您也许可以使用this 方法,但我不确定它如何与 async / await 混合使用。
  • 你试过了吗?快速浏览让我认为它会起作用。我没有看到任何会阻止它的异步阻塞问题。

标签: c# sql-server asynchronous


【解决方案1】:

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

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.

【讨论】:

  • 这是一个非常有趣的想法。我想知道它是否会大大增加处理时间。
  • 它不会显着增加处理时间。
  • 非常感谢您长时间周到的回答。我试图完全理解它。在 await SqlCommandWithProgress.ExecuteReader(constr, sql, s => Console.WriteLine(s))) 中,s => Console.WriteLine(s) 是不是像一个报告进度的函数?如果是,S 最初是从哪里得到的?
  • 该方法采用 Action 类型的参数,它是您提供的函数或代码的 sn-p,它采用 SessionStats 的实例并对其进行处理。在这种情况下,只需将其写入控制台。
【解决方案2】:

您将无法让 ExecuteNonQueryAsync 在这里做您想做的事情。要执行您要查找的操作,该方法的结果必须是在 SQL 调用期间逐行或按块递增,但这不是向 SQL Server 提交查询批处理的工作方式或您真正想要的方式从头顶的角度工作。您将一条 SQL 语句交给服务器,在它处理完该语句后,它会返回受该语句影响的总行数。

【讨论】:

  • 我想知道如果不是为了释放 UI 线程,他们为什么提供异步功能
  • @Missy 不是为了释放 UI 线程,而是释放 ASP.NET 线程池中的线程,以满足更多的用户请求。
【解决方案3】:

您是否只想让用户知道正在发生某些事情,而实际上不需要显示当前进度?

如果是这样,您可以只显示一个 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();
}

【讨论】:

  • 这发生在我身上,但我希望有一段简洁的代码可以在实用程序中使用和重用。进度条要求调用程序有进度条显示。
  • 我明白了.. 但是您当前的代码需要访问标签,对吧?差别不大。
  • 点了。我会考虑这个,但我希望能找到更多的捆绑解决方案。
  • 实现“捆绑”解决方案的一种方法(假设您坚持使用 winforms 世界)是弹出一个带有进度条的模式对话框。该表单足够简单,可以在运行时在您的方法中创建:您无需使用设计器。
  • 这是最直接的答案。请您看看我下面的想法并发表评论好吗?为了维护我的声誉,我也将不胜感激。然后我会选择这个作为回答。
【解决方案4】:

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

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

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

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

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

例如;本月活跃用户的数量决定了 FunctionThatCallsSQLandTakesTime 所花费的时间。每 10000 个用户需要 5 分钟。所以你可以相应地更新你的进度条。

【讨论】:

  • 解决问题的有趣方法。
【解决方案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);
    }

【讨论】:

  • 这主意不错,但实现有问题。你不应该在 UI 线程上Sleep。最好使用Timer 并在Tick 事件处理程序中进行处理。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多