【问题标题】:C# How is the CancellationToken used with SqlConnection.OpenAsync(token)?C# CancellationToken 如何与 SqlConnection.OpenAsync(token) 一起使用?
【发布时间】:2023-03-13 14:26:01
【问题描述】:

我正在尝试将 CancellationToken 与 SqlConnection.OpenAsync() 一起使用来限制 OpenAsync 函数所花费的时间。

我创建了一个新的 CancellationToken 并将其设置为在 200 毫秒后取消。然后我将它传递给 OpenAsync(token)。但是,此功能仍可能需要几秒钟才能运行。

查看文档我真的看不出我做错了什么。 https://docs.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlconnection.openasync?view=netframework-4.7.2

这是我正在使用的代码:

    private async void btnTestConnection_Click(object sender, EventArgs e)
    {
        SqlConnection connection = new SqlConnection(SQLConnectionString);
        Task.Run(() => QuickConnectionTest(connection)).Wait();
    }

    public async Task QuickConnectionTest(SqlConnection connection)
    {
        CancellationTokenSource source = new CancellationTokenSource();
        CancellationToken token = source.Token;
        source.CancelAfter(200);

        ConnectionOK = false;

        try
        {
            using (connection)
            {
                await connection.OpenAsync(token);

                if (connection.State == System.Data.ConnectionState.Open)
                {
                    ConnectionOK = true;
                }

            }
        }
        catch (Exception ex)
        {
            ErrorMessage = ex.ToString();
        }
    }

当 CancellationToken 在 200 毫秒过去后抛出 OperationCanceledException 时,我希望 OpenAsync() 提前结束,但它只是等待。

为了复制这一点,我执行以下操作:

  • 运行代码:结果 = 连接正常
  • 停止 SQL 服务
  • 运行代码:连接时长挂起。超时
  • 【问题讨论】:

    • 你不是在OpenAsync之前缺少一个await吗?
    • 你能有你的真实代码吗?
    • SqlConnection 已经内置了超时机制。默认值为 15 秒,使用 Azure 时建议为 30 秒。无论如何,这使得设置亚秒级超时似乎有些不切实际。
    • 如果我的帖子不清楚,请致歉。在找不到数据库的最坏情况下,我试图让超时时间小于 1 秒。 Connection.Timeout 不允许少于 1 秒,在测试时我发现测试连接可能需要 10 - 40 毫秒(在本地 PC 上)。

    标签: c# asynchronous


    【解决方案1】:

    CancelationToken 对其他人的作用与在您自己的代码中对您的作用相同。
    示例:

    class Program
    {
        static async Task Main(string[] args)
        {
            Console.WriteLine("main");
            var cts = new CancellationTokenSource();
            var task = SomethingAsync(cts.Token);
            cts.Cancel();
            await task;
            Console.WriteLine("Complete");
            Console.ReadKey();
        }
    
        static async Task SomethingAsync(CancellationToken token)
        {
            Console.WriteLine("Started");
            while (!token.IsCancellationRequested)
            {
                await Task.Delay(2000); //didn't pass token here because we want to simulate some work.
            }
            Console.WriteLine("Canceled");
        }
    }
    
    //**Outputs:**
    //main
    //Started
    //… then ~2 seconds later <- this isn't output
    //Canceled
    //Complete
    

    OpenAsync 方法可能需要一些优化,但不要指望调用Cancel() 会立即取消任何Task。这只是一个编组标志,让Task 中的工作单元知道调用者想要取消它。 Task 中的工作可以选择取消的方式和时间。如果您设置该标志时它很忙,那么您只需要等待并相信Task 正在做它需要做的事情来结束事情。

    【讨论】:

    • 你说的都是真的,但这如何解决他的问题呢?他需要快速取消。
    • 他需要取消并忘记。他不能强迫它停下来的速度比它要停下来的快。
    【解决方案2】:

    您的代码对我来说似乎是正确的。如果这没有按预期执行取消,则SqlConnection.OpenAsync 不支持可靠取消。取消是合作的。如果它没有得到适当的支持,或者存在错误,那么您无法保证。

    尝试升级到最新的 .NET Framework 版本。您还可以尝试似乎领先于 .NET Framework 的 .NET Core。我关注了 GitHub 存储库,发现了多个与异步相关的 ADO.NET 错误。

    您可以尝试在超时后拨打SqlConnection.Dispose()。也许这行得通。您也可以尝试使用同步 API (Open)。也许这使用了不同的代码路径。我相信不会,但值得一试。

    如果这不起作用,您需要编写代码,以便即使连接任务尚未完成,您的逻辑也会继续进行。假装它已经完成,让它在后台徘徊,直到它自己完成。这可能会导致资源使用量增加,但可能没问题。

    无论如何,请考虑在 GitHub corefx 存储库中打开一个问题,并使用最少的重现(例如连接到 example.com 的 5 行)。这应该可以工作。

    【讨论】:

      猜你喜欢
      • 2015-03-27
      • 2011-10-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-10-01
      相关资源
      最近更新 更多