【问题标题】:How to run a long stored procedure asynchronously and check on its progress如何异步运行长存储过程并检查其进度
【发布时间】:2021-01-19 22:48:30
【问题描述】:

我有一个 WPF 应用程序,它调用需要几分钟才能运行的 SQL Server 存储过程。此存储过程在其处理期间写入日志表。我想在存储过程运行时在网格中显示日志消息。

我有一个 while 循环,它在检查 IAsyncResult 中是否有 IsCompleted 时似乎正在工作,但在主存储过程完成之前,我的网格不会显示任何内容。如何在主存储过程运行时显示日志消息?

private void Button_Click(object sender, RoutedEventArgs e)
{
        string connString = @"Data Source=MyServer;Initial Catalog=MyDatabase;Integrated Security=True; Asynchronous Processing=True";

        try
        {
            using (SqlConnection conn = new SqlConnection(connString))
            {
                conn.Open();

                using (SqlCommand cmd = new SqlCommand("dbo.RunLongProcess", conn))
                {
                    cmd.CommandType = CommandType.StoredProcedure;
                    cmd.CommandTimeout = 0;

                    IAsyncResult result = cmd.BeginExecuteNonQuery();

                    while (!result.IsCompleted)
                    {
                        System.Threading.Thread.Sleep(1000);

                        using (SqlConnection connSteps = new SqlConnection(connString))
                        {
                            connSteps.Open();

                            using (SqlCommand cmdSteps = new SqlCommand("EXEC dbo.ReturnProcessSteps", connSteps))
                            {
                                using (SqlDataAdapter da = new SqlDataAdapter(cmdSteps))
                                {
                                    DataTable dt = new DataTable();
                                    da.Fill(dt);
                                    grdStepsTaken.ItemsSource = dt.DefaultView;
                                }
                            }

                            connSteps.Close();
                        }
                    }

                    cmd.EndExecuteNonQuery(result);
                }

                conn.Close();
            }
        }
        catch(Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
}

【问题讨论】:

  • 干净的方式:Progress<T>。肮脏的方式:Application.DoEvents。这些有帮助吗?
  • @RobJ,您需要在另一个线程中运行 ReturnProcessSteps 并根据需要更新 UI。尽可能避免 Application.DoEvents。
  • 因为代码永远不会赢得美丽奖:走脏路。看起来所有的逻辑都放在了点击事件和数据库中,UI访问,计算和逻辑都混合在一起了。
  • 谢谢,现在可以使用了。 Application.DoEvents 成功了。
  • @TheodorZoulias - 很多个月前,我正在开发一个 Windows 窗体应用程序,结果出现间歇性重入问题。试图找出原因是一团糟,结果Application.DoEvents() 是罪魁祸首——它不在堆栈跟踪中。太难找了。

标签: c# sql-server wpf asynchronous ado.net


【解决方案1】:

您在 cmets 中说过您只是在其中撞了一个 Application.DoEvents,但这仍然是解决问题的一种糟糕方法,因为似乎没有任何东西可以阻止用户再次点击,并且您堵塞了 UI线程一次 2 秒,睡眠

利用 TAP 并不难:

    private async void Button_Click(object sender, RoutedEventArgs e)
    {
        string connString = @"Data Source=MyServer;Initial Catalog=MyDatabase;Integrated Security=True; Asynchronous Processing=True";

        try
        {
            using (var cmd = new SqlCommand("dbo.RunLongProcess", connString))
            {
                cmd.Connection.Open();
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.CommandTimeout = 0;
                _timer.Start();
                await cmd.ExecuteNonQueryAsync();
                _timer.Stop();
            } 
        }
        catch(Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }

然后让 DispatcherTimer Tick 事件刷新数据:

    try{
      _timer.Stop();
      using (var da = new SqlDataAdapter("EXEC dbo.ReturnProcessSteps", connString))
      {
        DataTable dt = new DataTable();
        await Task.Run(() => da.Fill(dt));
        grdStepsTaken.ItemsSource = dt.DefaultView;
      }
    } finally{
      _timer.Start();
    }

DispatcherTimer 将确保使用 UI 线程执行事件。如果您准备自己进行编组,则可以省去它,使用普通的 Timers.Timer 并且填充时不需要异步,因为您要确保使用 UI 线程设置 ItemsSource。如果你不明白那句话的意义,就用一个 DispatcherTimer 并等待 ..

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-04-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-21
    • 2022-01-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多