如何在子任务上使用延续返回 Task<Object>

Posted

技术标签:

【中文标题】如何在子任务上使用延续返回 Task<Object>【英文标题】:How to return Task<Object> using continuation on a child task 【发布时间】:2020-07-21 22:15:06 【问题描述】:

我喜欢尽量减少相互等待但不显示为一个的任务的调度 我将如何更改我的 TestOption1 以返回调用方法的任务?

    [TestClass()]
    public class SqlServerTests
    
        public const string Membership = "Data Source=LocalHost;Initial Catalog=tempdb;Integrated Security=True;";

        [TestMethod()]
        public async Task ContinueWithTest()
        
            using CancellationTokenSource cts = new CancellationTokenSource();
            //warm up so pooling is enabled on all 3 methods
            using (var con = new SqlConnection(Membership))
            using (var cmd = con.CreateCommand())
            
                cmd.CommandType = System.Data.CommandType.Text;
                cmd.CommandText = "set nocount on";

                con.Open();
                cmd.ExecuteNonQuery();
            

            var sw2 = System.Diagnostics.Stopwatch.StartNew();
            await TestOption2(cts.Token).ConfigureAwait(false);
            sw2.Stop();

            //allow the benefit of the doubt for the slower and give it cashed plans
            var sw3 = System.Diagnostics.Stopwatch.StartNew();
            await TestOption3(cts.Token).ConfigureAwait(false);
            sw3.Stop();

            Assert.IsTrue(sw2.ElapsedTicks < sw3.ElapsedTicks, "Stopwatch 2 0 Stopwatch 3 1", sw2, sw3);


            var sw1 = System.Diagnostics.Stopwatch.StartNew();
            await TestOption1(cts.Token).ConfigureAwait(false);
            sw1.Stop();
            Console.WriteLine($"TestOption1: No internal awaits sw1.ElapsedTicks:N0 ticks");
            Console.WriteLine($"TestOption2: 1x internal await sw2.ElapsedTicks:N0 ticks");
            Console.WriteLine($"TestOption3: 2x internal await sw3.ElapsedTicks:N0 ticks");

            Assert.IsTrue(sw1.ElapsedTicks < sw2.ElapsedTicks, "Stopwatch 1 0 Stopwatch 2 1", sw1, sw2);
            Assert.IsTrue(sw1.ElapsedTicks < sw3.ElapsedTicks, "Stopwatch 1 0 Stopwatch 3 1", sw1, sw3);
        

        private static Task TestOption1(CancellationToken cancellationToken = default)
        
            using (var con = new SqlConnection(Membership))
            using (var cmd = con.CreateCommand())
            
                cmd.CommandType = System.Data.CommandType.Text;
                cmd.CommandText = "set nocount on";

                return con.OpenAsync(cancellationToken)//fails as it does not wait for the db to open....
                      .ContinueWith((t) => cmd.ExecuteNonQuery()
                                        , cancellationToken
                                        , continuationOptions: TaskContinuationOptions.ExecuteSynchronously
                                        , scheduler: TaskScheduler.Default);
            
        

        private static async Task TestOption2(CancellationToken cancellationToken = default)
        
            using (var con = new SqlConnection(Membership))
            using (var cmd = con.CreateCommand())
            
                cmd.CommandType = System.Data.CommandType.Text;
                cmd.CommandText = "set nocount on";

                await con.OpenAsync(cancellationToken)
                      .ContinueWith((_) => cmd.ExecuteNonQuery(), cancellationToken).ConfigureAwait(false);
            
        

        private static async Task TestOption3(CancellationToken cancellationToken = default)
        
            using (var con = new SqlConnection(Membership))
            using (var cmd = con.CreateCommand())
            
                cmd.CommandType = System.Data.CommandType.Text;
                cmd.CommandText = "set nocount on";

                await con.OpenAsync(cancellationToken).ConfigureAwait(false);
                await cmd.ExecuteNonQueryAsync().ConfigureAwait(false);
            
        
    

我希望能够做这样的事情

    [TestMethod]
    public async Task TestContinueWithDelegate()
    
        var data = await TestOptionReturn().ConfigureAwait(false);
        Assert.IsNotNull(data);
    

    private static Task<object> TestOptionReturn(CancellationToken cancellationToken = default)
    
        using (var con = new SqlConnection(Membership))
        using (var cmd = con.CreateCommand())
        
            cmd.CommandType = System.Data.CommandType.StoredProcedure;
            cmd.CommandText = "Test1";

            return con.OpenAsync(cancellationToken)//fails as it does not wait for the db to open....
                  .ContinueWith(delegate  return cmd.ExecuteScalar(); 
                                    , cancellationToken
                                    , continuationOptions: TaskContinuationOptions.ExecuteSynchronously
                                    , scheduler: TaskScheduler.Default);
        
    

由于数据库未打开,以下测试失败

[测试方法] 公共异步任务 TestContinueWithDelegate() 使用 CancellationTokenSource cts = new CancellationTokenSource(); var 数据 = 等待 TestOptioDDL(cts.Token).ConfigureAwait(false); 使用 var reader = await TestOptionOutput(cts.Token).ConfigureAwait(false); 断言.IsNotNull(数据); 断言.IsTrue(reader.Read());

        private static Task<object> TestOptioDDL(CancellationToken cancellationToken = default)
        
            using (var con = new SqlConnection(Membership))
            using (var cmd = con.CreateCommand())
            
                cmd.CommandType = System.Data.CommandType.StoredProcedure;
                cmd.CommandText = "TestOutput";
                cmd.Parameters.Add(new SqlParameter("Data", System.Data.SqlDbType.DateTime)  IsNullable = true, Direction = System.Data.ParameterDirection.Output );
                return con.OpenAsync(cancellationToken)//fails as it does not wait for the db to open....
                      .ContinueWith(delegate
                      
                          cmd.ExecuteScalar();
                          return cmd.Parameters[0].Value;
                      
                      , cancellationToken
                      , continuationOptions: TaskContinuationOptions.ExecuteSynchronously
                      , scheduler: TaskScheduler.Default);
            
        

        private static Task<SqlDataReader> TestOptionOutput(CancellationToken cancellationToken = default)
        
            using (var con = new SqlConnection(Membership))
            using (var cmd = con.CreateCommand())
            
                cmd.CommandType = System.Data.CommandType.Text;
                cmd.CommandText = "select * from sys.databases";
                cmd.Parameters.Add(new SqlParameter("Data", System.Data.SqlDbType.DateTime)  IsNullable = true, Direction = System.Data.ParameterDirection.Output );
                return con.OpenAsync(cancellationToken)//fails as it does not wait for the db to open....
                      .ContinueWith(delegate
                      
                          return cmd.ExecuteReader();
                      
                      , cancellationToken
                      , continuationOptions: TaskContinuationOptions.ExecuteSynchronously
                      , scheduler: TaskScheduler.Default);
            
        

【问题讨论】:

你能详细描述一下你到底想做什么吗? 如果你已经确定你的连接在同步模式下工作,试试这个:var t1=con.OpenAsync(cancellationToken); t1.ContinueWith(delegate return cmd.ExecuteReader(); , cancellationToken , continuationOptions: TaskContinuationOptions.ExecuteSynchronously , scheduler: TaskScheduler.Default); return t1;这能解决你的问题吗? @SinaHoseinkhani,不,同样的错误 【参考方案1】:

TestOption3 是最好的选择。针对可维护性进行优化,而不是为了在 I/O 高度密集的任务上节省几毫秒。

也就是说,您的连接已关闭,因为您的连接正在处理before the tasks complete(可能在它甚至还没有打开之前!)。如果您要删除asyncawait,那么您需要处理重写您的方法,以便using 末尾的处理将在任务完成后运行

【讨论】:

@Stehen 您能否在博客中发布一个示例,我相信有很多开发人员正在寻找这个。我的大部分任务都非常快,并且异步切换成为一个问题,特别是当服务器上的负载变大时 我很想知道您是如何断定问题在于异步切换的?请注意,您现有的测试时间无效; TestOption1 实际上并没有运行该命令,因为连接已关闭。 测试 1-3 被过度简化,因此所有人都可以尝试执行它,当运行 1 个生产代码时,我们在插入、更新和删除并且不需要事务时使用选项 1,很多非关键数据的日志记录,将其更改为较慢的内容,这真的很痛

以上是关于如何在子任务上使用延续返回 Task<Object>的主要内容,如果未能解决你的问题,请参考以下文章

合并不同类型的 .NET 4.0 任务/延续

如何防止任务的同步延续?

在子查询中使用多个表

聊聊多线程哪一些事儿(task)之 二 延续操作

对于代表返回 void 的操作的任务,Task.FromResult<T>() 的替代方法是啥?

从 Microsoft UWP 上的异步方法返回 Task<string>