ExtremeSwat
ExtremeSwat

Reputation: 824

Async Event on C# Best Practice Confusion

I have some doubt related to some best practices regarding the asynchronous events. More exactly related to assigning the async modifier to a void method (that triggers an EventHandler)

My app's supposed to work to do some tests on the background and when I finish up the execution to upload my results to my Db.

Before asking the question I've taken a look over here but I still feel that I am doing something wrong.

From what I've tested there is no apparent reason (for my specific case) to apply the async modifier when I am calling the event handler because specifying the async modifier to subscriber's underlying methods it'll 'dial-back' when meeting the first await resuming the program execution until the awaited action finishes up, working perfectly as I wanted.

My doubt came when I started applying (1'st approach) the async modifier to a void method (the WorkerClass) even though it's the event handler, am I doing it wrongly ?

I've done the following test : Made a stored procedure which delays it's execution for about a minute

alter procedure TestStoredProcedure
    @WhileValue int
as
begin
    set nocount on;

    begin transaction
    begin try

        waitfor delay '00:01';

        insert into WhileResults(Value)
            values
                (@WhileValue)

        commit tran;
    end try
    begin catch
        raisError('Error',16,1);
        rollback tran;
    end catch;

    return;
end
go

This is my 1'st approach :

class Program
    {
        static void Main(string[] args)
        {
            var workerClass = new WorkerClass();
            var engine = new PlaybackEngine();
            engine.TestFinishedEventHandler += workerClass.WorkSomething;

            engine.TestRun();
        }
    }


    class PlaybackEngine
    {
        public EventHandler TestFinishedEventHandler;

        public void TestRun()
        {
            var i = 0;
            while (i < 10000)
            {
                Console.WriteLine(i);
                i++;    

                OnTestFinishedEventHandler(i,new EventArgs());
            }
        }

        protected virtual void OnTestFinishedEventHandler(object sender, EventArgs args)
        {
            TestFinishedEventHandler?.Invoke(sender,args);
        }

    }

    class WorkerClass
    {
        public async void WorkSomething(object sender, EventArgs args)
        {
            await UploadToDbAsync((int)sender);
        }

        private async Task UploadToDbAsync(int i)
        {
            using (var sqlConn = new SqlConnection("Data Source=EDWARD;Initial Catalog=TestDb;Integrated Security=True"))
            using (var sqlCommand = new SqlCommand())
            {
                sqlConn.Open();
                sqlCommand.Connection = sqlConn;
                sqlCommand.CommandTimeout = 1000000;

                sqlCommand.CommandType = CommandType.StoredProcedure;
                sqlCommand.CommandText = "dbo.TestStoredProcedure";

                sqlCommand.Parameters.AddWithValue("@WhileValue", i);

                await sqlCommand.ExecuteNonQueryAsync();

                sqlConn.Close();
            }
        }
    }

My second approach is :

class Program
    {
        static void Main(string[] args)
        {
            var workerClass = new WorkerClass();
            var engine = new PlaybackEngine();
           // engine.TestFinishedEventHandler += workerClass.WorkSomething;
            engine.TestFinished += workerClass.WorkSomething;

            engine.TestRun();
        }
    }

    class PlaybackEngine
    {
        public delegate Task TestRunEventHandler(object source, EventArgs args);
        public event TestRunEventHandler TestFinished;
        public void TestRun()
        {
            var i = 0;
            while (i < 10000)
            {        
                /* Doing some work here */        
                i++; 
                OnTestRan(i,new EventArgs());
                Console.WriteLine(i);
            }
        }

        protected virtual async void OnTestRan(object source, EventArgs args)
        {
            //await TestFinished?.Invoke(source, EventArgs.Empty);
            if (TestFinished != null)
                await TestFinished(source, new EventArgs());
        }
    }

    class WorkerClass
    {
        public async Task WorkSomething(object sender, EventArgs args)
        {
            await UploadToDbAsync((int)sender);
        }

        private async Task UploadToDbAsync(int i)
        {
            using (var sqlConn = new SqlConnection("Data Source=EDWARD;Initial Catalog=TestDb;Integrated Security=True"))
            using (var sqlCommand = new SqlCommand())
            {
                sqlConn.Open();
                sqlCommand.Connection = sqlConn;
                sqlCommand.CommandTimeout = 1000000;

                sqlCommand.CommandType = CommandType.StoredProcedure;
                sqlCommand.CommandText = "dbo.TestStoredProcedure";

                sqlCommand.Parameters.AddWithValue("@WhileValue", i);

                await sqlCommand.ExecuteNonQueryAsync();
                sqlConn.Close();
            }
        }

Upvotes: 1

Views: 262

Answers (1)

Kerim Emurla
Kerim Emurla

Reputation: 1151

My doubt came when I started applying (1'st approach) the async modifier to a void method (the WorkerClass) even though it's the event handler, am I doing it wrongly ?

No, you're doing it correctly and you should not have any doubts. You should avoid using async void, unless it's an event handler. That's the only acceptable place to use async void.

Async/Await - Best Practices in Asynchronous Programming - You should read this article if you are interested in more best practices about asynchronous programming.

Upvotes: 3

Related Questions