0
votes

I like to minimize the scheduling of tasks that wait on eachother but not show it's one How would I alter my TestOption1 to return a Task the the calling method?

Task continuations

    [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);
            }
        }
    }

I would like to be able to do something like this

    [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);
        }
    }

The following tests fail as the database doesn't open enter image description here

[TestMethod] public async Task TestContinueWithDelegate() { using CancellationTokenSource cts = new CancellationTokenSource(); var data = await TestOptioDDL(cts.Token).ConfigureAwait(false); using var reader = await TestOptionOutput(cts.Token).ConfigureAwait(false); Assert.IsNotNull(data); Assert.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);
            }
        }
1
Can you describe more about what exactly do you want to do?Sina Hoseinkhani
If you are already sure that your connection works in synchronous mode, try this: var t1=con.OpenAsync(cancellationToken); t1.ContinueWith(delegate { return cmd.ExecuteReader(); } , cancellationToken , continuationOptions: TaskContinuationOptions.ExecuteSynchronously , scheduler: TaskScheduler.Default); return t1; does this solve your problem?Sina Hoseinkhani
@SinaHoseinkhani, nope, same errorWalter Vehoeven

1 Answers

2
votes

TestOption3 is the best choice. Optimize for maintainability, not for saving a few milliseconds on what will be a highly I/O-bound task.

That said, your connection is closed because your connection is being disposed before the tasks complete (possibly before it's even opened!). If you're going to remove async and await, then you need to handle rewriting your method so that the disposal at the end of the using will be run after the tasks complete.