【问题标题】:How to stop a method (triggered by button click) in WPF如何在 WPF 中停止方法(通过按钮单击触发)
【发布时间】:2021-06-10 09:58:03
【问题描述】:

我的 WPF 中有一个私有 async void Button_Click 方法,它运行一个非常复杂的 SQL 查询,可以运行几分钟。 我希望用户可以通过单击另一个按钮来停止此方法。 我的代码是这样的:

public partial class MainWindow : Window  
{
  private async void Button_Click(object sender, RoutedEventArgs e)
  {
     string SQL_Query= " a very long and complicated SQL query ... "
     SqlCommand SQL_Query_cmd = new SqlCommand(SQL_Query, conn);
    
     DataTable dt = new DataTable();
     await Task.Run(() => {
     using (SqlDataAdapter a = new SqlDataAdapter(SQL_Query_cmd))
     { a.Fill(dt);}
     });

  }
}

我在这个链接How to use WPF Background Worker 中读到了BackgroundWorker。 但不明白如何将它集成到我的代码中。我认为,我的“填充数据表”代码已经是异步的,但我不知道如何停止它。假设要结束这个方法的按钮叫stop_btn,它的Click方法叫cancelButton_Click

请在帖子中写下您的答案,而不是 cmets。我将不胜感激。

【问题讨论】:

标签: c# wpf multithreading backgroundworker


【解决方案1】:

您可以使用IDbCommand.Cancel 方法和CancellationTokenSource 在服务器端和客户端执行取消操作。

private IDbCommand _activeSqlCommand;
private CancellationTokenSource _cts;

private async void btnExecute_Click(object sender, RoutedEventArgs e)
{
    // The _activeSqlCommand and _cts should be null here.
    // Otherwise, you may end up with multiple concurrent executions.
    Debug.Assert(_activeSqlCommand == null);
    Debug.Assert(_cts == null);
    var sqlQuery = "A very long and complicated SQL query...";
    var localSqlCommand = new SqlCommand(sqlQuery, _connection);
    var localCts = new CancellationTokenSource();
    _activeSqlCommand = localSqlCommand;
    _cts = localCts;
    btnExecute.IsEnabled = false;
    btnCancel.IsEnabled = true;
    try
    {
        DataTable dataTable = await AsCancelable(Task.Run(() =>
        {
            var dt = new DataTable();
            using (SqlDataAdapter a = new SqlDataAdapter(localSqlCommand))
                a.Fill(dt);
            return dt;
        }, localCts.Token), localCts.Token);
        // Here use the dataTable to update the UI
    }
    catch (OperationCanceledException) { } // Ignore
    catch (SqlException ex) when (ex.ErrorCode == CANCEL_ERROR_CODE) { } // Ignore
    finally
    {
        btnCancel.IsEnabled = false;
        btnExecute.IsEnabled = true;
        // The _activeSqlCommand and _cts should still have the local values here.
        Debug.Assert(_activeSqlCommand == localSqlCommand);
        Debug.Assert(_cts == localCts);
        _activeSqlCommand = null;
        _cts = null;
        localCts.Dispose();
    }
}

private void btnCancel_Click(object sender, RoutedEventArgs e)
{
    _activeSqlCommand?.Cancel();
    _cts?.Cancel();
}

private static Task<T> AsCancelable<T>(Task<T> task,
    CancellationToken cancellationToken)
{
    var cancelable = new Task<T>(() => default, cancellationToken);
    return Task.WhenAny(task, cancelable).Unwrap();
}

您必须弄清楚在取消执行时数据库服务器会抛出什么样的异常,并根据其ErrorCode 或其他一些属性忽略此异常。

【讨论】:

  • 非常感谢。我正在努力理解。到目前为止,我收到了一些关于我的正常 SQL 查询执行的错误。我是否可以要求您将与取消数据库执行相关的部分分开并注释掉其余部分。我希望我至少可以实现那部分。
  • @Iraj 您可以注释掉_cts?.Cancel(); 行,这样CancellationTokenSource 就不会被取消。这样,btnCancel_Click 将不会响应,并且如果数据库服务器决定由于某种原因无法取消查询,则可能根本没有响应。
  • 我现在遇到的问题是应该在数据网格中显示信息的命令不起作用。即:dataGrid1.ItemsSource = dt.DefaultView;
  • @Iraj dt 现在是 Task.Run 委托中的内部变量。要更新 UI,您可以使用 dataTable 变量。
  • 非常感谢
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多