如何在 .NET 中取消 Task.Wait() 之后正在运行的 OpenAsync()?

Posted

技术标签:

【中文标题】如何在 .NET 中取消 Task.Wait() 之后正在运行的 OpenAsync()?【英文标题】:How can I cancel a running OpenAsync() After Task.Wait() in .NET? 【发布时间】:2021-07-05 21:34:31 【问题描述】:

我想随时通过客户端单击按钮连接到数据库。当客户端使用不同的 ConnectionString 多次单击按钮时。现在我只想返回客户端创建的最后一个连接。因此,我必须取消最后一个任务并创建新任务。问题是如何在调用 Task.wait() 使用 CancellationToken 后取消任务?因为当我还没有调用wait()时,任务状态会是WaitForActive,所以我永远无法收到结果。请帮我解决这个案子。我的代码草稿如下:

    private SqlConnection _connection = null;
    private CancellationTokenSource _source = null;
    private Task<string> _result = null;
    
    public Task<string> GetStatusAsync(string connectionString)
    
        if (_source == null)
            _source = new CancellationTokenSource();
        if (_connection != null && _connection.State == ConnectionState.Connecting)
        
            _source.Cancel();
            _source = new CancellationTokenSource();
        

        _result = ConnectDatabaseAsync(connectionString.Trim(),_source.Token);
        _result.Wait();
        return _result;
    

    private async Task<string> ConnectDatabaseAsync(string connectionString, CancellationToken cancellationToken)
                            
        if (_connection == null)
            _connection = new SqlConnection();
        try
        
            _connection.ConnectionString = connectionString;
            await _connection.OpenAsync(cancellationToken);
            return "ok";
        
        catch (Exception ex)
        
            return ex.Message;
        
        finally
        
            if (_connection.State == ConnectionState.Open)
                _connection.Close();
        
    

【问题讨论】:

你不应该使用_result.Wait() - 这会阻塞线程,因此用户将永远无法点击按钮 - 你的整个应用程序被冻结 @torvin 谢谢。我知道。 await 任务将与 Wait() 相同,因为当调用 await 或 wait() 时,我的任务将永远不会启动。 您使用的是 WinForm 还是 Web 应用程序? @Saeed Winform。你有什么想法吗? 【参考方案1】:

不要因为冻结主线程而对任务使用等待。

您可以像这样使用任务字典和取消令牌来管理您的任务(我只是模拟您的方法):

      private static readonly Dictionary<Task, CancellationTokenSource> Tasks = new Dictionary<Task, CancellationTokenSource>();
      public async Task<string> GetStatusAsync()
        
            var source = new CancellationTokenSource();
            var task = ConnectDatabaseAsync("YourConnection", source.Token);

            if (Tasks.Any())
                foreach (var item in Tasks)
                
                    if (!item.Key.IsCompleted && !item.Value.IsCancellationRequested)
                        item.Value.Cancel();
                

            Tasks.Add(task, source);

            await task;

            return task.Result;
        

        private async Task<string> ConnectDatabaseAsync(string connection, CancellationToken token)
        
            await Task.Delay(10000, token);

            return "Ok";
        

【讨论】:

我可以看到你有一个条件是!item.Key.IsCompleted &amp;&amp; !item.Value.IsCancellationRequested 并调用cancel() 方法取消。那么,Cancel() 方法兄弟在哪里?如果 !item.Value.IsCancellationRequested 为真,也许我不想杀死最后一个任务。 @StevenTrinh 使用火灾取消令牌,您可以结束任务。当您从 CancellationTokenSource 调用 Cancel 方法时,您会取消该任务并且不需要额外的取消方法。对于那个If,当item.Key.IsCompleted 为真时意味着你的任务已经完成并且不能取消它,当item.Value.IsCancellationRequested 为真时意味着任务已经被取消,不需要取消它。 @StevenTrinh 你测试过这个答案吗?没事吧? 已经检查过了。这里有问题。那是 OpenAsync() 运行的时候。它将首先检查 CancellationToken 然后调用 Open() 方法进行连接。所以,稍后,我调用 Cancel() 但它不会取消 OpenAsync() 因为它已经退出了检查 canceltoken 部分。 @StevenTrinh 否,如果取消令牌在运行任务期间触发,它将取消任务。你的问题是在新的传入任务之前取消队列中的所有任务,我认为这个解决方案会这样做。

以上是关于如何在 .NET 中取消 Task.Wait() 之后正在运行的 OpenAsync()?的主要内容,如果未能解决你的问题,请参考以下文章

在 C# 中使用 Web 服务:删除 Task.Wait()

使用Task.Wait而不是等待异步编程

我应该使用 Task.Wait() 吗?

使用 CancellationToken 取消任务而不在任务中明确检查?

Task.Wait 总是返回 false 虽然任务完成

Task用法-任务等待wait