C# CancellationToken 如何与 SqlConnection.OpenAsync(token) 一起使用?

Posted

技术标签:

【中文标题】C# CancellationToken 如何与 SqlConnection.OpenAsync(token) 一起使用?【英文标题】:C# How is the CancellationToken used with SqlConnection.OpenAsync(token)? 【发布时间】:2019-06-06 14:40:29 【问题描述】:

我正在尝试将 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吗? referencesource.microsoft.com/#System.Data/System/Data/… 你能有你的真实代码吗? SqlConnection 已经内置了超时机制。默认值为 15 秒,使用 Azure 时建议为 30 秒。无论如何,这使得设置亚秒级超时似乎有些不切实际。 如果我的帖子不清楚,请致歉。在找不到数据库的最坏情况下,我试图让超时时间小于 1 秒。 Connection.Timeout 不允许少于 1 秒,在测试时我发现测试连接可能需要 10 - 40 毫秒(在本地 PC 上)。 【参考方案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 行)。这应该可以工作。

【讨论】:

以上是关于C# CancellationToken 如何与 SqlConnection.OpenAsync(token) 一起使用?的主要内容,如果未能解决你的问题,请参考以下文章

C#源码生成器的cancellationtoken啥时候取消?

如何使用 CancellationToken 属性?

我应该何时调用 CancellationToken.ThrowIfCancellationRequested?

我应该如何使用 DataflowBlockOptions.CancellationToken?

如何取消 CancellationToken

我们应该将 CancellationToken 与 MVC/Web API 控制器一起使用吗?